目录数据库
12.1 什么是事务处理... 1编程
12.2 事务处理控制语句... 1数据结构
12.2.1 COMMIT处理... 2并发
12.2.2 ROLL BACK处理... 2oracle
12.2.3 SAVEPOINT和ROLL BACK TO SAVEPOINT. 3函数
12.2.4 SET TRANSACTION.. 3工具
试验:冻结视图... 4oop
12.2.5 SET CONSTRAINTS. 5性能
12.3 事务处理的ACID属性... 7学习
12.3.1 原子性... 7
12.3.2 一致性... 7
试验:事务处理级别的一致性... 8
12.3.3 隔离性... 11
12.3.4 持久性... 11
12.4 并发控制... 11
12.4.1 锁定... 12
试验:引起死锁... 12
12.4.2 多版本和读取一致性... 15
考虑一个进一步的示例。... 16
12.5 小结... 17
开发者可以命名他们的PL/SQL程序块,为它们肯定参数,将它们存储在数据库中,而且从任何数据库客户或者实用工具中引用或者运行它们,例如 SQL*Plus、Pro*C,甚至是JDBC。
这此听PL/SQL程序称为存储过程和函数。它们的集合称为程序包。在本章中,咱们将要解释使用过程、函数和程序包的三大优点、这三种类似结构之间的区别。
Oracle 9i产品帮助文档:
http://docs.oracle.com/cd/B10501_01/index.htm
可根据本身须要进行查询,包含了众多的文档。
Sample Schemas的目录:
http://docs.oracle.com/cd/B10501_01/server.920/a96539/toc.htm
Sample Schemas的文档(示例模式的表及介绍):
http://docs.oracle.com/cd/B10501_01/server.920/a96539.pdf
在讨论这2个特性的时候,咱们将要在本章中学习以下内容:
●Oracle中的事务处理是什么
●怎样控制Oracle中的事务控制
●Oracle怎样在数据库中实现并发控制,让多个用户同时访问和修改相同的数据表
用于有效记录某机构感兴趣的业务活动(称为事务)的数据处理(例如销售、供货的定购或货币传输)。一般,联机事务处理 (OLTP) 系统执行大量的相对较小的事务。
Oracle中的一个重要概念就是没有“开始事务处理”的语句。用户不能显式开始一个事务处理。事务处理会隐式地开始于第一条修改数据的语句,或者一些要求事务处理的场合。使用COMMIT或者ROLL BACK语句将会显式终止事务处理。
如上所述,事务处理具备原子性,也就是说,或者全部语句都成功执行,或者全部语句都不能成功执行。咱们会在这里介绍其中一些成员:
●COMMIT
●ROLL BACK
●SAVEPOINT
●ROLL BACK TO<SAVEPOINT>
●SET TRANSACTION
●SET CONSTRAINT(S)
做为开发者,用户应该使用COMMIT或者ROLL BACK显式终止用户的事务处理,不然用户正在使用的工具/环境就将为用户选取其中一种方式。
不管事务处理的规模如何,提交都是很是快速的操做。用户可能会认为事务处理越大(换句话说,影响的数据越多),提交所耗费时间越长。事实并不是如此,进化论事务处理规模如何,提交的响应时间一般都很“平缓”。这是由于提交实际上没有太多的工做去作,可是它所作的工做却相当重要。
若是可以理解这点,那么就能够避免许多开发者所采用的工做方式,去限制他们的事务处理规模,每隔若干行就进行提交,而不是当逻辑单元的工做已经执行完毕以后才进行提交。他们这样作是由于他们错误地认为他们正在下降系统资源的负载;而事实上,他们正在增长负载。若是提交一行须要耗费X个时间单元,那么提交1000行也会消耗相同的X个时间单元,而采用1000次提交1行的方式完成这项工做就须要额外运行1000*X个时间单元。若是只在必要的时候进行一次提交(当事务处理完成的时候),那么用户就不只能够提升性能,并且能够减小对共享资源(日志文件,保护SGA内共享数据结构的锁定)的争用。
那么,为何不管事务处理的规模如何,提交的响应时间都至关平缓呢?这是由于在数据库中进行提交以前,咱们已经完成了全部实际工做,咱们已经修改了数据库中的数据,完成了99.9%的工做。
当提交的时候,咱们还要执行三个任务:
●为咱们的事务处理生成 SCN(系统改变编号)。这是Oracle的内部时钟,能够称为数据库时间。SCN不是传统意义上的时钟,由于它不是随着时间失衡而递进。相反,它是在事务处理提交的时候递进,由Oracle在内部使用,以对事务处理排序。
●将全部剩余的已经缓冲的重作日志表项写入磁盘,而且将SCN记录到在重作日志文件中。这要由LGWR执行。
●释放咱们的会话(以及全部正在等等咱们所占有锁定的用户)所占有的全部锁定。
LGWR不会一直缓冲完成的全部工做,而是会随着操做的进行,在后台不断清理重作日志缓冲的内容,这很是相似于在PC机上运行的磁盘缓冲软件。它能够避免在清理全部的用户重作日志时,提交操做出现长时间等待现象。LGWR会在如下状况执行清理工做:
●每隔3秒
●当SGA中的日志缓冲超过了1/3的空间,或者包含了1MB或者更多的已缓冲数据
●进行任何事务处理提交
因此,即便咱们长时间运行事务处理,也会有部分它所产生的已缓冲重作日志在提交以前写入了磁盘。
当咱们进行回滚的时候,咱们还有一些任务须要执行:
●撤销全部已经执行的改变。这要经过读取咱们生成的UNDO数据,有效地反转咱们的操做来完成。若是咱们插入了一行,那么回滚就要删除它。若是咱们更新了一行,回滚就要将其更新到原来的样子。若是咱们删除了一行,就要从新插入它。
●释放咱们的会话(以及正在等等咱们已经锁定的行的用户)占用的全部锁定。
SAVEPOINT可让用户在事务处理中创建标记点。用户能够在单独的事务处理中拥有多个保存点。当使用ROLL BACK TO <SAVEPOINT NAME>的时候,它就可让用户有选择地回滚更大的事务处理中的一组语句。
保存点是委有用的事务处理特性,它们可让用户将单独的大规模事务处理分割成一系列较小的部分。拟执行的全部语句仍然是较大的事务处理的组成部分,用户能够将特定的语句组织到一块儿,将它们做为单独的语句进行回滚。
为了作到这一点,用户须要在开始函数的时候使用以下的SQL语句:
Savepoint function_name;
这里的function_name是用户的函数名称。若是用户在处理期间遇到错误,用户就只需使用:
Roll back to function_name;
这个语句可使您设置事务处理的各类属性,例如它的隔离层(参考如下的隔离层次列表),它是只读仍是能够进行读写,以及是否要使用特定的回滚段。
SET TRANSACTION语句必须是事务处理中使用的第一个语句。这就是说,必须在任何INSERT、UPDATE或者DELETE语句,以及任何其它能够开始事务处理的语句以前使用它。SET TRANSACTION的做用域只是当前的事务处理。
SET TRANSACTION语句可让用户:
●规定事务处理隔离层次。
●规定为用户事务处理所使用的特定回滚段。
●命名用户事务处理。
咱们将要在这里使用的重要SET TRANSACTION语句包括:
●SET TRANSACTION READ ONLY
●SET TRANSACTION READ WRITE
●SET TRANSACTION ISOLACTION LEVEL SERIALIZABLE
●SET TRANSACTION ISOLACTION LEVEL READ COMMITTED
注意:
SET TRANSACTION语句事实上都是互斥的。例如,若是您选择了READ ONLY,那么就不能为它选择READ WRITE、SERIALIZABLE或者READ COMMITTED。
命令SET TRANSACTION READ ONLY将会作2件事,它会确保您没法执行修改数据的DML操做,例如INSERT、UPDATE或者DELETE。以下所示:
SQL> set transaction read only; 事务处理集。 SQL> update emp set ename=lower(ename); update emp set ename=lower(ename) * ERROR 位于第 1 行: ORA-01456: 不能够在 READ ONLY 事务处理中执行插入/删除/更新操做
其效果很明显。而READ ONLY事务处理的另外一个反作用则更为微妙。经过将事务处理设置为READ ONLY,咱们就能够有效地将咱们的数据库视图冻结到某个时间点。个人意思是,不管数据库中的其它会话如何工做,数据库在咱们的面前都会是使用SET TRANSACTION语句时候样子。这有什么用处呢?咱们假定如今是一个繁忙的工做日的下午1点。用户须要运行一个报表。这个报表将要执行几十条查询,从许多数据源中获取数据。全部这些数据都会相互关联,为了使其有意义,它们必须保持一致。换句话说,用户须要每一个查询都看到“1点钟”的数据库。若是每一个查询看不一样时间点的数据库,那么咱们报告中的结果就没有意义。
为了确保用户数据一致,用户应该锁定报表查询所需的全部表,防止其它用户更新它们。做为一种替代的方法,用户可使用SET TRANSACTION READ ONLY语句,冻结“1点钟”的用户数据库视图。这能够获得一箭双鵰的结果。
(1) 为了运行这个示例,用户须要在SQL*Plus中打开3个会话。创建一个表。
SQL> create table t as select object_id from all_objects where rownum<=2000; 表已建立。
(2) 用以观察READ ONLY和READ WRITE事务处理区别。
时间 |
会话1 |
会话2 |
会话3 |
注释 |
T1 |
set transaction read only; |
|
|
|
T2 |
select count(*) from t; |
select count(*) from t; |
|
2个事务处理均可以看到2000行 |
T3 |
|
|
delete from t where rownum<=500; |
从T中删除500行,可是没有提交 |
T4 |
select count(*) from t; |
select count(*) from t; |
|
因为多版本的做用,因此这2个会话都会看到2000行 |
T5 |
|
|
commit; |
让500个已删除的行永久删除 |
T6 |
select count(*) from t; |
select count(*) from t; |
|
会话1仍然会看到2000行,会话2如今将要看到1500行!到提交或者回滚为止,会话1将会一直看到200行 |
T7 |
|
|
insert into t select * from t; |
对T的规模进行加倍,使其达到3000个记录 |
T8 |
|
|
commit; |
使得改变可见 |
T9 |
select count(*) from t; |
select count(*) from t; |
|
会话1会继续看到2000行。会话2如今能够看到全部的3000行 |
T10 |
commit; |
|
|
|
T11 |
select count(*) from t; |
|
|
会话1如今也能够看到3000行。 |
第二个命令SET TRANSACTION READ WRITE不会常用,由于它是默认设置。不多有必要使用这个命令,在这里包含它只是出于完整性的考虑。
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE与READ ONLY有一些相似。当使用了这个命令以后,不管是否出现改变,数据库都会为您进行“冻结”。就如同咱们在READ ONLY事务处理中看到的那样,您能够彻底隔离其它事务处理的影响。
这个命令大致至关于将事务处理设置为READ WRITE,因为它是设置隔离层次时Oracle的默认操做模式,因此不多使用。若是您在会话的前面使用ALTER SESSION命令,将用户会话的事务处理的默认隔离层次从READ COMMITTED改变为SERIABLIZABLE,那么就能够会用到这个命令。使用ISOLATION LEVEL READ COMMITTED命令能够重置默认值。
在Oracle中,约束能够在DML语句执行后当即生效,也能够延迟到事务处理提交的时候才生效。SET CONSTRAINT语句可让您在事务处理中设置延迟约束的强制模式。可使用以下语法延迟单独的约束:
set constraint constraint_name defferred
或者,您也可使用以下语法延迟全部约束:
set constraints all defferred
在这些命令中,您可使用关键字IMMEDIATE代替DEFERRED,将他们的强制模式改回当即模式。为了看到实际的命令,咱们可使用一个简单的示例:
SQL> drop table t; 表已丢弃。 SQL> create table t 2 (x int, 3 constraint x_greater_than_zero check(x>0) 4 deferrable initially immediate 5 ) 6 / 表已建立。 SQL> insert into t values(-1); insert into t values(-1) * ERROR 位于第 1 行: ORA-02290: 违反检查约束条件 (SCOTT.X_GREATER_THAN_ZERO)
不错,咱们没法向X中插入值-1.如今,咱们使用SET CONSTRAINT命令延迟约束的脸证,以下所示:
SQL> set constraint x_greater_than_zero deferred; 约束条件已设置。 SQL> insert into t values(-1); 已建立 1 行。
然而,咱们没法在数据库中提交这条语句:
SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事务处理已重算 ORA-02290: 违反检查约束条件 (SCOTT.X_GREATER_THAN_ZERO)
有2种方法使用这个命令,或者规定一组约束名称,或者使用关键词ALL。例如,为了延迟约束X_GREATER_THAN_ZERO,咱们可使用:
set constraint x_greater_than_zero deferred;
咱们也能够很容易地使用以下命令:
set constraints all deferred;
这里的区别是第2个版本使用了ALL,它会影响咱们会话中的全部延迟约束,而不仅是咱们感兴趣的约束。并且,为了明确使用用户约束,也可使用以下语句:
set constraint <constraint_name> immediate;
例如,咱们能够在以上的救命中使用以下语句:
SQL> set constraint x_greater_than_zero deferred; 约束条件已设置。 SQL> insert into t values(-1); 已建立 1 行。 SQL> set constraint x_greater_than_zero immediate; SET constraint x_greater_than_zero immediate * ERROR 位于第 1 行: ORA-02290: 违反检查约束条件 (SCOTT.X_GREATER_THAN_ZERO) SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事务处理已重算 ORA-02290: 违反检查约束条件 (SCOTT.X_GREATER_THAN_ZERO)
Oracle会回滚咱们的事务处理。经过SET CONSTRAINT <constraint_name> IMMEDIATE命令,咱们就可以发现咱们已经违反了一些约束,而咱们的事务处理仍然有效。这个约束将会处理延迟模式,它不会当即检查。
ACID是替代以下内容的首字母缩写:
●原子性(Atomicity)——事务处理要么所有进行,要么不进行。
●一致性(Consistency)——事务处理要将数据库从一种状态变成另外一种状态。
●隔离性(Isolation)——在事务处理提交以前,事务处理的效果不能由系统中其它事务处理看到。
●持久性(Durability)——一旦提交了事务处理,它就永久生效。
在Oracle中,事务处理具备原子性。换句话说,或者提交全部的工做,或者什么工做都不提交。
这是很是重要的事务处理特性,任何事务处理都会将数据库从一种逻辑上的一致状态转变为另外一种逻辑上的一致状态。这就是说,在事务处理开始以前,数据库的全部数据都会知足您已经施加给数据库的业务规则(约束)。
咱们所使用的表和触发器以下所示:
SQL> drop table t; 表已丢弃。 SQL> create table t 2 ( x int, 3 constraint t_pk primary key(x) 4 ) 5 / 表已建立。 SQL> create trigger t_trigger 2 after update on T for each row 3 begin 4 dbms_output.put_line('Updated x='||:old.x||' to x='||:new.x); 5 end; 6 / 触发器已建立
如今,咱们要向T中插入一些行:
SQL> insert into t values(1); 已建立 1 行。 SQL> insert into t values(2); 已建立 1 行。
这就结束了这个示例的设置。如今,物体 尝试对表T进行更新,将全部行都设置为数值2。
SQL> set serverout on SQL> begin 2 update t set x=2; 3 end; 4 / Updated x=1 to x=2 Updated x=2 to x=2 begin * ERROR 位于第 1 行: ORA-00001: 违反惟一约束条件 (SCOTT.T_PK) ORA-06512: 在line 2
如今,若是咱们使用以下所示的成功语句:
SQL> begin 2 update t set x=x+1; 3 end; 4 / Updated x=1 to x=2 Updated x=2 to x=3 PL/SQL 过程已成功完成。
数据库在逻辑上保持了一致(它知足了全部的业务规则),因此语句将会成功。
(1) 咱们要创建2个表PARENT和CHILD。CHILD表具备PARENT表上的外键,这个外键要定义为可延迟。
SQL> create table parent(pk int, 2 constraint parent_pk primary key(pk)); 表已建立。 SQL> create table child(fk, 2 constraint child_fk foreign key(fk) 3 references parent deferrable); 表已建立。
(2) 如今,咱们使用一些数据生成这些表。
SQL> insert into parent values(1); 已建立 1 行。 SQL> insert into child values(1); 已建立 1 行。
(3) 如今,咱们尝试更新PARENT表,改变它的主键值。
SQL> update parent set pk=2; update parent set pk=2 * ERROR 位于第 1 行: ORA-02292: 违反完整约束条件 (SCOTT.CHILD_FK) - 已找到子记录日志
(4) 为了解决这个问题,咱们只需告诉Oracle咱们将要延迟约束CHILD_FK,也就是说,咱们不想它在语句层次进行检查。
SQL> set constraints child_fk deferred; 约束条件已设置。
(5) 如今,咱们能够正确地更新PARENT表
SQL> update parent set pk=2; 已更新 1 行。 SQL> select * from parent; PK ---------- 2 SQL> select * from child; FK ---------- 1 SQL> commit; commit * ERROR 位于第 1 行: ORA-02091: 事务处理已重算 ORA-02292: 违反完整约束条件 (SCOTT.CHILD_FK) - 已找到子记录日志
(6) 咱们再次进行尝试,看看如何让Oracle验证咱们数据的逻辑一致性,而不进行回滚。咱们首先会再次设置约束DEFERRED,而后再次更新父表,重作Oracle刚才撤销的工做。
SQL> set constraints child_fk deferred; 约束条件已设置。 SQL> update parent set pk=2; 已更新 1 行。 SQL> select * from parent; PK ---------- 2 SQL> select * from child; FK ---------- 1
(7) 咱们如今将CHILD_FK约束设置为IMMEDIATE。这将致使Oracle当即验证咱们业务规则的一致性。
SQL> set constraints child_fk immediate; SET constraints child_fk immediate * ERROR 位于第 1 行: ORA-02291: 违反完整约束条件 (SCOTT.CHILD_FK) - 未找到父项关键字
(8) 如今,咱们能够更新CHILD记录,再次检查约束而且提交
SQL> update child set fk=2; 已更新 1 行。 SQL> set constraints child_fk immediate; 约束条件已设置。 SQL> commit; 提交完成。 SQL> select * from parent; PK ---------- 2 SQL> select * from child; FK ---------- 2
这将会致使一致性数据知足咱们的约束。
在给定用户隔离层次的状况下,使用相同的输入,采用相同的方式执行的相同的工做可能会致使不一样的答案,这些隔离层次采用指定层次上许可(或者不符合规定)的三种“读取”方式进行定义。它们是:
●脏读取(Dirty read)——这种读取方式的含义同它听起来同样糟糕。您能够读取没有提交的“脏”数据。
●非可重复读取(Non-repeatable read)——这种方式意味着,若是用户在T1时刻读取了一行,在T2时刻再次读取一行,那么这个行就可能发生改变。例如,它可能已经被删除或者更新。
●影像读取(Phantom read)——这种方式意味着若是用户在T1时刻执行了一个查询,在T2时刻再次执行它,就可能会有影响结果的附加行加入到数据库中。在这种状况下,这种读取方式与非可重复读取有所不一样,您已经读取的数据不会发生变化,而是会有比之前更多的知足查询条件的数据。
SQL92采用了这三种“读取”方式,而且基于它们的存在与否创建了4种隔离层次,见表13-2,它们是:
隔离层次 |
脏读取 |
非重复读取 |
影像读取 |
非提交读取(Read Uncommitted) |
容许 |
容许 |
容许 |
提交读取(Read committed) |
禁止 |
容许 |
容许 |
可重复读取(Repeatable Read) |
禁止 |
禁止 |
容许 |
串行读取(Serializable) |
禁止 |
禁止 |
禁止 |
持久性是数据库提供的最重要的特性之一。它能够确保一旦事务处理提交以后,它的改变就会永久生效。它们不会因为系统故障或者错误而“消失”。
开发多用户、数据库驱动的应用 的主要挑战之一就是要最大化并发访问(多个用户同时访问数据),并且与此同时,还要确保每一个用户都能在一致的方式下读取和修改数据。可以提供这些功能的锁定和并发控制是全部数据库的关键特性。
锁定(lock)是用来控制共享资源并发访问的机制。要注意,咱们使用术语“共享资源”,而没有使用“数据库行”或者“数据库表”。Oracle确实能够在行级别上锁定表数据,可是它还能够在许多不一样的层次上使用锁定,提供对各类资源的并发访问。
大多数状况下,锁对于做为开发者的用户来说是透明的。当更新数据行的时候,咱们没必要对其进行锁定,Oracle会为咱们完成这些工做。有些时候显式锁定是必须的,可是大多数时候,锁定会自行管理。
在单用户的数据库中,锁根本没有必要。由于根据定义,在这种状况下只有一个用户会修改信息。然而,当多用户访问或者修改数据或者数据结构的时候,具备能够防止并发访问修改相同信息的机制就分外重要。这就是锁定的价值。
当2个用户打败了2者都但愿使用的资源时就会出现死锁。
Oracle处理死锁的方式很是简单。当检测出死锁的时候(它们就会马上被检测出来),Oracle就会选择一个会话做为“牺牲者”。
(1) 咱们要创建2个所须要的表,以下所示:
SQL> create table a as select 1 x from dual; 表已建立。 SQL> create table b as select 1 x from dual; 表已建立。
(2) 如今,打开SQL*Plus会话,而且更新表A。
SQL> update a set x=x+1; 已更新 1 行。
(3) 新打开一个SQL*Plus会话,更新B表。
SQL> update b set x=x+1; 已更新 1 行。
(4) 在相同的会话,再一次更新a表
SQL> update a set x=x+1;
会发生死锁。
能够在许多层次上进行锁定。用户能够在一行上拥有锁定,或者实际上也能够在一个表上拥有锁定。一些数据库(尽管没有Oracle)还能够在数据库层次上锁定数据。在一些数据库中,锁定是稀缺的资源,拥有许多锁定能够负面地影响系统的性能。在这些数据库中,你能够发现为了保存这些资源,用户的100个行级别的锁定会转换为一个表级别的锁定。这个过程就称为锁定升级(lock escalation)。它使得系统下降了用户锁定的粒度。
锁定升级不是数据库想要的属性。事实上,数据库支持升级锁定暗示着它的锁定机制存在固有的系统开销,管理上百个锁定是须要处理的重要工做。在Oracle中,拥有一个锁定或者上百万个锁定的系统开销是相同的——没有区别。
Oracle会使用锁定转换(lock conversion)或者提高(promotion)。它将会在尽量级别上获取锁定,而且将锁定转换到更具限制性的级别上。例如,若是使用FOR UPDATE子句从表中选取一行,那么就会应用2个锁定。一个锁定会放置在您所选择的行上。这个锁定是的,它将会阻止其它的用户锁定指定行。另外一个锁定要放置在表上,它是一个ROW SHARE TABLE锁。这个锁将会阻止其它会话获取表上的排它锁。
阻塞(Blocking)会在一个会话拥有另外一个会话正在请求的资源上的锁定时出现。正在进行请示的会话一直阻塞,直到占用资源的会话翻译锁定资源为止。例如,USER一、USER2同时查询,1分钟后,USER1修改了USER3的地址,USER2的会话仍然为1分钟前的数据,修改了USER3的电话号码,此时,USER2的旧数据替换了USER1修改的数据。
悲观锁定(Pessimistic locking)听起来很差,可是相信我,它实际并不是如此。在使用悲观锁定的时候,您就是在代表“我相信,某人颇有可能会改变我正在读取(而且最终要更新)的相同数据,所以,在咱们花费时间,改变数据以前,我要锁定数据库中的行,防止其它会话更新它”。
为了完成这项工做,咱们要使用相似以下语句的查询:
select * from table where column1 = : old_column1 and column2 = : old.column2 and... and primary_key = : old_primary_key for update nowait;
因此咱们将会从这个语句中获得三种结果:
●咱们将要获取咱们的行,而且对这个行进行锁定,防止被其它会话更新(可是阻止读取)。
●咱们将会获得ORA-00054 Resource Busy错误。其它的人已经锁定了这个行,咱们必需要等待它。
●因为某人已经对行进行了改变,因此咱们将会返回0行。
因为咱们要在进行更新以前对行进行锁定,因此这称为悲观锁定(pessimistic locking)。咱们对行维持不被改动并不乐观(所以命名为悲观)。用户应用中的活动流程应该以下所示:
●不进行锁定查询数据
SQL> select empno,ename,sal from emp where deptno=10; EMPNO ENAME SAL ---------- ---------- ---------- 7782 CLARK 2450 7839 KING 5000 7934 MILLER 1300
●容许终端用户“查询”数据。
SQL> select empno,ename,sal 2 from emp 3 where empno=7934 4 and ename='MILLER' 5 and sal=1300 6 for update nowait 7 / EMPNO ENAME SAL ---------- ---------- ---------- 7934 MILLER 1300
●假如咱们锁定了数据行,咱们的应用就可使用更新,提交改变
SQL> update emp 2 set ename='miller' 3 where empno=7934; 已更新 1 行。 SQL> commit; 提交完成。
在Oracle中,悲观锁定能够运行良好。它可让用户相信他们正在屏幕上修改的数据当前由他们所“拥有”。若是用户要离开,或者一段时间没有使用记录,用户就要让应用释放锁定,或者也能够在数据库中使用资源描述文件设定空闲会话超时。这样就能够避免用户锁定数据行,而后回家过夜的问题。
第二种方法称为乐观锁定(optimistic locking),这经会在应用中同时保存旧值和新值。
咱们首先做为用户SCOTT登陆,而且打开2个SQL*Plus会话,咱们要做为2个独立的用户(USER1和USER2)。而后,2个用户都要使用如下的SELECT语句。
USER1
SQL> select * from dept where deptno=10; DEPTNO DNAME LOC ---------- -------------- ------------- 10 ACCOUNTING NEW YORK
USER1更新
SQL> update dept 2 set loc='BOSTON' 3 where deptno=10; 已更新 1 行。
USER2更新
SQL> update dept 2 set loc='ALBANY' 3 where deptno=10; 已更新 1 行。
DEPTNO=10的LOC=’BOSTON’的记录再也不存在,因此这里更新了0行。咱们乐观地认为更新能够完成。
悲观锁定最大的优点在于终端用户能够在他们花费时间进行修改以前,就可以发现他们的改变不可以进行。而在乐观锁定的状况中,终端用户将会花费大量的时间进行修改。
Oracle使用了多版本读取一致性并发模型。从根本讲,Oracle经过这个机制能够提供:
●读取一致性查询(Read-consistent queries):可以产生结果与相应时间点一致的查询。
●非阻塞查询(Non-blocking queries):查询不会被数据定入器阻塞。
SQL> drop table t; 表已丢弃。 SQL> create table t as select * from all_users; 表已建立。 SQL> set serverout on SQL> declare 2 cursor c1 is select username from t; 3 l_username varchar2(30); 4 begin 5 open c1; 6 delete from t; 7 commit; 8 loop 9 fetch c1 into l_username; 10 exit when c1%notfound; 11 dbms_output.put_line(l_username); 12 end loop; 13 close c1; 14 end; 15 / SYS SYSTEM OUTLN DBSNMP WMSYS ORDSYS ORDPLUGINS MDSYS …… PL/SQL 过程已成功完成。
虽然从表中删除全部数据,咱们甚至能够提交删除工做。这些行都不见了,咱们也会这样认为。事实上,它们还能够经过游标得到。实际上,经过OPEN命令返回给咱们的结果集在打开它的时候就已经预先肯定了。咱们在获取数据以前没有办法知道答案是什么,可是从游标的角度来看,结果是不变的。并非Oracle在咱们打开游标的时候,将全部以上数据复制到了其它的位置;事实上是删除操做做为咱们保留了数据,将它们放置到了UNDO表空间或者回滚段中。
创建一个ACCOUNTS的表。可使用以下方式创建它:
SQL> create table accounts 2 ( 3 account_id number, 4 account_type varchar2(20), 5 balance number 6 ); 表已建立。
精确快速地报告BALANCE总数。用户须要报告时间以及全部帐户余额总和:
SQL> select current_timestamp,sum(balance) total_balance from accounts;
注意:
CURRENT_TIMESTAMP是Oracle 9i的内建列,它将会返回当前的日期和时间。在较早的Oracle版本中,用户须要使用SYSDATE。
然而,若是当咱们进行处理的时候,这个数据库表上要应用成百上千个事务处理,那么问题就会稍微复杂一些。人们会从他们的储蓄帐号向支票帐号划转资金,他们还要进行取款、存款(咱们多是一个很是繁忙的银行)等。
咱们假定ACCOUNTS表如表13-3所示:
表13-3 ACCOUNTS表示例
ACCOUNT_ID |
ACCOUNT_TYPE |
BALANCE |
1234 |
储蓄 |
100 |
5678 |
支票 |
4371 |
2542 |
储蓄 |
6232 |
7653 |
储蓄 |
234 |
…<上百万行> |
|
|
1234 |
支票 |
100 |
咱们的示例将要有2个结局,一个结局将要展现当查询检测到锁定灵气的时候会出现什么状况,另外一个结局展现当查询检测到数据已经发生改变,它不可以看到它们的时候会出现什么状况。
表13-4 查询遇到锁定数据
时间 |
事件 |
注释 |
T1 |
咱们在会话1中开始查询W(如今有150) |
它要开始对表进行读取,因为ACCOUNTS表很是大,因此这 须要花几分钟的时间。这个查询已经读取了“第一行”的ACCOUNT_ID为1234储蓄帐号,可是尚未到达支票帐号 |
T2 |
帐号1234的全部者在ATM上开始事务处理 |
|
T3 |
帐号1234的全部者选取TRANSFERFUNDS,而且选取从他们的支票帐号向储蓄帐号划转50美金(150-50) |
数据库中的数据进行了更新,因此支票帐号如今具备50美金,而储蓄帐号具备150美金。工做尚未提交(可是已经存储了UNDO信息) |
T4 |
咱们查询最终到达ACCOUNT_ID为1234的支票帐户行 |
这时发生什么呢?在其它大多数流行数据库中,答案是“查询将要等待”。而Oracle中不会这样 |
T5 |
因为检测到数据已经被T3时刻执行的工做所锁定,因此咱们的查询将要接受到UNDO信息(数据“之前的”映像),而且使用T1时刻的数据映像 |
Oracle将要讲到锁定,它不会等待。咱们查询将要在支票帐号读取到100美金 |
T6 |
咱们的报告生成 |
|
T7 |
ATM提交而且完成 |
|
本将操做的有意思的部分发生在以上时间链T5时刻
表13-5 查询遇到已经改变的数据
时间 |
事件 |
注释 |
T4 |
ATM会话进行提交,完成转帐 |
资金发生转移,另外的会话如今能够看到在ACCOUNT_ID为1234的储蓄帐号中有150美金,支票帐号中有50美金 |
T5 |
咱们查询最终到达ACCOUNT_ID为1234的支票帐户行 |
它再也不锁定,没有人正在更新它 |
T6 |
因为检测到在T1时刻以后数据已经进行了修改,咱们的查询将会接收到UNDO信息,而且使用T1时刻的数据映像 |
个人查询将要再次为支票帐号读取100美金 |
T6 |
咱们的报告生成 |
|
简单来讲,Oracle除了删除,在更新、增长中,可以把UNDO表空间或者回滚段中的数据读取出来,来为客户展现它数据的一致性。
在本章中,咱们首先了事务处理的构成。了解了事务处理控制语句,以及怎样和在何时使用它们。
当讨论并发控制的时候,咱们讨论了2个重要而又复杂的主题,它们是锁定和多版本读取一致性。了解了死锁、跟踪文件分析死锁、数据库中避免阻塞等待的不一样模式、悲观锁定和乐观锁定。
文章根据本身理解浓缩,仅供参考。
摘自:《Oracle编程入门经典》 清华大学出版社 http://www.tup.com.cn