`
nully
  • 浏览: 8454 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

(转)数据库事务

 
阅读更多
8.8 数据库事务
数据库事务(transaction)就是一组SQL语句,这组SQL语句是一个逻辑工作单元。可以认为事务就是一组不可分割的SQL语句,其结果应该作为一个整体永久性地修改数据库的内容,或者作为一个整体取消对数据库的修改。

数据库事务的一个例子是将钱从一个银行账号中转到另外一个银行账号中。此时通常包含两步操作:一条UPDATE语句负责从一个银行账号的总额中减去一定的钱数,另外一条UPDATE语句负责向另外一个银行账号中增加相应的钱数。减少和增加这两个操作必须永久性地记录到数据库中,否则钱就会丢失。如果钱的转帐有问题,则必须同时取消减少和增加这两个操作。这个简单的例子只使用了2个UPDATE语句,然而更实际的事务通常都可以包含多个INSERT、UPDATE和DELETE语句。

8.8.1 事务的提交和回滚
要永久性地记录事务中SQL语句的结果,需要执行COMMIT语句,从而提交(commit)事务。要取消SQL语句的结果,需要执行ROLLBACK语句,从而回滚(rollback)事务,将所有行重新设置为原始状态。

下面这个例子向customers表中添加一行,然后执行COMMIT语句,永久性地保存对数据库所进行的修改:

*INSERT INTO customers

VALUES (6, 'Fred', 'Green', '01-JAN-1970', '800-555-1215');

1 row created.

COMMIT;

Commit complete.

下面这个例子更新顾客#1的内容,然后执行一条ROLLBACK语句,取消对数据库所进行的修改:

*UPDATE customers

SET first_name = 'Edward'

WHERE customer_id = 1;

1 row updated.

ROLLBACK;

Rollback complete.

下面这个查询展示了由执行COMMIT语句而产生的新行:

*SELECT *

FROM customers;

CUSTOMER_ID FIRST_NAME LAST_NAME DOB          PHONE

----------- ---------- ---------- --------- ------------

1 John        Brown      01-JAN-65 800-555-1211

2 Cynthia    Green      05-FEB-68 800-555-1212

3 Steve      White      16-MAR-71 800-555-1213

4 Gail       Black                   800-555-1214

5 Doreen     Blue       20-MAY-70

6 Fred       Green      01-JAN-70 800-555-1215

注意顾客 #6的记录被COMMIT语句永久性地保存到数据库中,但顾客 #1名字的变化却被ROLLBACK语句取消了。

8.8.2 事务的开始与结束
事务是用来分割SQL语句的逻辑工作单元。事务既有起点,也有终点;当下列事件之一发生时,事务就开始了:

●       连接到数据库上,并执行一条DML语句(INSERT、UPDATE或DELETE)。

●       前一个事务结束后,又输入了另外一条DML语句。

当下列事件之一发生时,事务就结束了:

●       执行COMMIT或ROLLBACK语句。

●       执行一条DDL语句,例如CREATE TABLE语句;在这种情况下,会自动执行COMMIT语句。

●       执行一条DCL语句,例如GRANT语句;在这种情况下,会自动执行COMMIT语句。GRANT语句将在下一章中介绍。

●       断开与数据库的连接。在退出SQL*Plus时,通常会输入EXIT命令,此时会自动执行COMMIT语句。如果SQL*Plus被意外终止了(例如运行SQL*Plus的计算机崩溃了),那么就会自动执行ROLLBACK语句。这适用于任何可能访问数据库的程序。例如,如果编写了一个可以访问数据库的Java程序,而这个程序崩溃了,那么就会自动执行ROLLBACK语句。

●       执行了一条DML语句,该语句却失败了;在这种情况中,会为这个无效的DML语句执行ROLLBACK语句。

数据库事务 - 广BOYY - 广纳博客のYYblog

提示:

不显式地提交或回滚事务被认为是不好的编程习惯,因此确保在每个事务后面都要执行COMMIT或ROLLBACK语句。

8.8.3 保存点
在事务中的任何地方都可以设置一个保存点(savepoint),这样可以将修改回滚到保存点处。如果有一个很大的事务,这将非常有用,因为这样如果在保存点后进行了误操作,并不需要将整个事务一直回滚到最开头。但是保存点不能肆意乱用;最好是重新调整一下事务的结构,将其转换为较小的事务。

下面将给出一个使用保存点的例子,但是在开始之前,我们首先来检查一下产品#4和#5的当前价格:

*SELECT product_id, price

FROM products

WHERE product_id IN (4, 5);

PRODUCT_ID      PRICE

---------- ---------

         4      13.95

           5      49.99

产品#4的价格是$13.95,而产品#5的价格是$49.99。接下来,将产品#4的价格增加20%:

*UPDATE products

SET price = price * 1.20

WHERE product_id = 4;

1 row updated.

下面这条语句设置一个保存点,并将其命名为save1:

*SAVEPOINT save1;

Savepoint created.

此后运行的DML语句都可以回滚到这一保存点,而对产品#4所做的修改则会保留。

下面这条UPDATE语句将产品#5的价格增加30%:

*UPDATE products

SET price = price * 1.30

WHERE product_id = 5;

1 row updated.

下面这个查询得到这两个产品的价格:

*SELECT product_id, price

FROM products

WHERE product_id IN (4, 5);

PRODUCT_ID      PRICE

---------- ---------

          4      16.74

          5      64.99

产品#4的价格增加了20%,而产品#5的价格增加了30%。下面这条语句将这个事务回滚到刚才设置的保存点处:

*ROLLBACK TO SAVEPOINT save1;

Rollback complete.

这样可以取消对产品#5价格所做的改变,但保留了对产品#4价格所做的改变。下面这个查询展示了这一点:

*SELECT product_id, price

FROM products

WHERE product_id IN (4, 5);

PRODUCT_ID      PRICE

---------- ---------

          4      16.74

        5      49.99

不出所料,产品#4的价格增加了,但产品#5的价格仍是原来的值。下面这条ROLLBACK语句取消这整个事务:

*ROLLBACK;

Rollback complete.

这会取消对产品#4的价格所做的改变,下面这个查询可以展示这一点:

*SELECT product_id, price

FROM products

WHERE product_id IN (4, 5);

PRODUCT_ID      PRICE

---------- ---------

         4      13.95

        5      49.99

8.8.4 事务的ACID特性
在前面我们将事务定义为逻辑工作单元,即一组相关的SQL语句,它们要么作为一个单位被提交,要么作为一个单位被回滚。数据库理论对事务采用了更严格的定义,说明事务有4个基本的特性,称为ACID特性(ACID来自于下面列出的每个特性的首字母):

●       原子性(Atomic) 事务是原子的,这就是说一个事务中包含的所有SQL语句都是一个不可分割的工作单元。

●       一致性(Consist) 事务必须确保数据库的状态保持一致,这就是说事务开始时,数据库的状态是一致的;在事务结束时,数据库的状态也必须是一致的。

●       隔离性(Isolated) 多个事务可以独立运行,而不会彼此产生影响。

●       持久性(Durable) 一旦事务被提交之后,数据库的变化就会被永远保留下来,即使运行数据库软件的机器后来崩溃也是如此。

Oracle数据库软件确保每个事务都具有ACID特性,并且具有非常丰富的恢复特性,可以在系统崩溃后恢复数据库。

8.8.5 并发事务
Oracle数据库软件支持多个用户同时与数据库进行交互,每个用户都可以同时运行自己的事务。这种事务就称为并发事务(concurrent transaction)。

如果用户同时运行多个事务,而这些事务都对同一个表产生影响,那么这些事务的影响都是独立的,直到执行一条COMMIT语句时才会彼此产生影响。下面这个例子中使用了两个事务T1和T2,这两个事务都会访问customers表,以下的事件序列展示了事务的隔离性:

(1) T1和T2分别执行SELECT语句,检索customers表的所有行。

(2) T1执行INSERT语句,向customers表中插入数据,但T1此时并不执行COMMIT语句。

(3) T2再执行一条SELECT语句,检索结果与步骤1中相同。T2并没有“看到”T1在步骤2中插入的新行。

(4) T1最后执行COMMIT语句,永久性地保存在步骤2中插入的新行。

(5) T2执行一条SELECT语句,最终看到了T1所插入的新行。

以上过程可以归纳如下:直到T1提交自己对数据库所做的修改之后,T2才能看到数据库的变化。这是事务间隔离性的默认级别。但是,正如8.8.7小节“事务隔离性级别”中将要介绍的,隔离性级别可以修改。

表8-1给出了SQL语句的例子,这可以进一步阐明并发事务的工作方式。该表显示了两个事务T1和T2要执行的语句的交叉顺序。事务T1对customers表进行检索、新添一行并修改其中一行的内容。事务T2对customers表进行检索。直到T1提交自己对数据库所做的修改之后,T2才能看到数据库的变化。读者可以启动两个单独的SQL*Plus会话,并都以store用户的身份连接到数据库上,此时输入表8-1中所列出的语句,并查看各个语句的结果。要按照表中所给出的交叉次序在SQL*Plus会话中输入这些语句。

表8-1 并 发 事 务

事务1 T1

事务2 T2

(1) SELECT *

FROM customers;

(2) SELECT *

FROM customers;

(3) INSERT INTO customers (

customer_id, first_name, last_name

) VALUES (

7, 'Jason', 'Price'

);

(4) UPDATE customers

SET last_name = 'Orange'

WHERE customer_id = 2;

(5) SELECT *

FROM customers;

返回结果集中包含新插入的行和修改后的结果

(6) SELECT *

FROM customers;

返回结果集中不包含事务T1所插入的行和修改后的结果。相反,结果集中包含的是第2步中检索出的原来的行

(7) COMMIT;

这会提交新行和所做的修改

(8) SELECT *

FROM customers;

返回结果集中包含事务T1在第3步和第4步中所插入的行和修改后的结果

8.8.6 事务锁
要支持并发事务,Oracle数据库软件必须确保表中的数据一直有效。这可以通过锁(lock)来实现。考虑下面这个例子:两个事务T1和T2试图修改customers表中顾客#1的记录:

(1) T1执行一条UPDATE语句修改顾客#1的记录,但是T1并没有执行COMMIT语句。此时就称为T1对该行“加锁”了。

(2) T2也试图执行一条UPDATE语句修改顾客#1的记录,但是由于该行早已被T1加锁了,因此T2现在就不能获得该行的锁。T2的UPDATE语句必须一直等,直到T1结束并释放该行上的锁为止。

(3) T1执行COMMIT语句并结束,从而释放该行上的锁。

(4) T2获得该行上的锁,并执行UPDATE语句。T2获得该行上的锁后一直持有,直到T2结束为止。

以上过程归纳如下:当一个事务已经拥有某一行上的锁时,另外一个事务不能获得该行上的锁。

数据库事务 - 广BOYY - 广纳博客のYYblog

注意:

理解默认加锁机制的最简单的方法如下:读程序(reader)不会阻塞读程序;写程序(writer)不会阻塞读程序;只有在试图对相同的行进行修改时,写程序才会阻塞写程序。

8.8.7 事务隔离级别
事务隔离性级别(transaction isolation level)是一个事务对数据库的修改与并行的另外一个事务的隔离程度。在详细了解各种事务隔离性级别之前,首先需要理解在当前事务试图访问表中的相同行时可能会出现哪些问题。

下面将给出几个例子,其中两个并发事务T1和T2正在访问相同的行,这几个例子可以展示出事务处理中可能存在的三种问题:

●       幻像读取(phantom read) 事务T1读取一条指定的WHERE子句所返回的结果集。然后事务T2新插入一行记录,这行记录恰好可以满足T1所使用查询中的WHERE子句的条件。然后T1又使用相同的查询再次对表进行检索,但是此时却看到了事务T2刚才插入的新行。这个新行就称为“幻像”,因为对于T1来说这一行就像是变魔术似地突然出现了一样。

●       不可重复读取(nonrepeatabl read) 事务T1读取一行记录,紧接着事务T2修改了T1刚才读取的那一行记录的内容。然后T1又再次读取这一行记录,发现它与刚才读取的结果不同了。这中现象称为“不可重复”读,因为T1原来读取的那一行记录已经发生了变化。

●       脏读(dirty read) 事务T1更新了一行记录的内容,但是并没有提交所做的修改。事务T2读取更新后的行。然后T1执行回滚操作,取消了刚才所做的修改。现在T2所读取的行就无效了(也称为“脏”数据),因为在T2读取这行记录时,T1所做的修改并没有提交。

为了处理这些可能出现的问题,数据库实现了不同级别的事务隔离性,以防止并发事务会相互影响。SQL标准定义了以下几种事务隔离级别,按照隔离性级别从低到高依次为:

●       READ UNCOMMITTED 幻像读、不可重复读和脏读都允许。

●       READ COMMITTED 允许幻像读和不可重复读,但是不允许脏读。

●       REPEATABLE READ 允许幻像读,但是不允许不可重复读和脏读。

●       SERIALIZABLE 幻影读、不可重复读和脏读都不允许。

Oracle数据库支持READ COMMITTED和SERIALIZABLE两种事务隔离性级别,不支持READ UNCOMMITTED和REPEATABLE READ这两种隔离性级别。

虽然SQL标准定义的默认事务隔离性级别是SERIALIZABLE,但是Oracle数据库默认使用的事务隔离性级别却是READ COMMITTED,这几乎对于所有应用程序来说都是可以接受的。

数据库事务 - 广BOYY - 广纳博客のYYblog

警告:

虽然在Oracle数据库中也可以使用SERIALIZABLE的事务隔离性级别,但是这会增加SQL语句执行所需要的时间,因此只有在必须的情况下才应该使用SERIALIZABLE级别。

事务隔离性级别可以使用SET TRANSACTION语句设置。例如,下面这个语句就将事务隔离性级别设置为SERIALIZABLE:

*SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;

接下来将介绍一个使用SERIALIZABLE事务隔离性级别的例子。

8.8.8 SERIALIZABLE事务的一个例子
本节将介绍一个例子,该例可以展示将事务隔离性级别设置为SERIALIZABLE后的效果。

本例使用了两个事务:T1和T2。T1使用默认的隔离性级别READ COMMITTED;而T2使用的隔离性级别为SERIALIZABLE。T1和T2都将读customers表中的行,然后T1将向customers表中新插入一行,并修改该表中的一行记录。由于T2的隔离性级别是SERIALIZABLE,因此它并不能“看到”事务T1新插入的行或更新过的行,即使在T1提交所做的修改之后也是如此。这是因为读取新插入行的操作是一次幻影读操作,而读取修改过的行的操作是一次不可重复读操作,这两种操作在SERIALIZABLE类型的事务中都是不允许的。

表8-2列出了构成T1和T2的SQL语句,这些语句的次序就是它们被实际执行的顺序。

表8-2 SERIALIZABLE类型的事务

事务1 T1(READ COMMITTED级别)

事务2 T2(SERIALIZABLE级别)

(1) SET TRANSACTION ISOLATION

LEVEL SERIALIZABLE;

(3) SELECT *

FROM customers;

(2) SELECT *

FROM customers;

(4) INSERT INTO customers (

customer_id, first_name, last_name

) VALUES (

8, ‘Steve’, ‘Button’

);

(5) UPDATE customers

SET last_name = ‘Yellow’

WHERE customer_id = 3;

(6) COMMIT;

(7) SELECT *

FROM customers;

返回的结果集中包含插入的新行和修改后的结果

(8) SELECT *

FROM customers;

返回的结果集中仍然不包含事务T1所插入的新行和修改后的结果。因为T2是SERIALIZABLE级别的

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics