oracle 事物概述

来源:互联网 发布:停车场收费系统数据库 编辑:程序博客网 时间:2024/06/05 09:22

事务的概念都是关系数据库范例的一部分。事务由一个或多个DML语句组成,后面紧跟着ROLLBACK或COMMIT命令。可以在事务内使用SAVEPOINT命令给出控制程度。

1.数据库事务

oracle确保事务完整性的机制是撤销段和重做日志文件的组合:此机制无疑是迄今为止开发的任何数据库中的翘楚,而且完全符合数据处理的国际标准。虽然其他数据库供应商能够通过使用自己的机制遵循相同的标准,但是却改变了效率级别。简而言之,任何关系数据库都必须能够通过ACID测试,必须确保原子性(Atomicity)、一致性(Consistency)、隔离性(Isolation)和持久性(Durability)。

1.原子性

原子性原则规定一个事务的所有部分必须都完成,或都不完成(该术语背后的理论是原子不可再分——现在大家知道这是一个错误的假设)。例如,如果业务分析师认为每次某个雇员的薪水发生变化时都必须更改该雇员的等级,那么这个”原子“事务就由两个更新操作组成。数据库必须保证两个更新操作都能完成或者都不完成。如果只有一个更新操作被成功执行,那么雇员的薪水与等级会出现矛盾(商业术语为数据损坏)。如果事务在完成之前出现任何错误,那么数据库自身就必须保证自动回滚所做过的任何事务部分(并且必须自动进行)。虽然原子事务听起来似乎像原子一样小,然而事实上它可以非常大。再举一个例子,从逻辑上看,某个财务软件的名目账项总账不能为八、九月份各半月的账目之和,因此月末转账就是一个原子事务,这个事务可能会影响数千个表中的几百万条记录,并且需要花费几个小时才能完成(或者在出现错误时所进行的回滚)。未完成事务的回滚可以是人工进行的(此时需要执行ROLLBACK命令),不过在出现错误的情况下,回滚必须是自动的和不可停止的。

2.一致性

一致性的原则规定查询的结果必须与数据库在查询开始时的状态一致。假设要进行一个简单的查询,该查询会求取某个表中某列数据的平均值。如果这个表很大,那么扫描整个表需要数分钟时间。在这个查询进行期间,如果其他用户也在更新该列,那么查询应当包括新值还是旧值?查询应当包含在查询启动之后被插入或被删除的记录吗?一致性的原则要求数据库确保查询不会看见改变后的值;无论这个查询经过多长时间或者发生与指定表相关的其他任何动作,它都会提供查询开始时指定列所有数据的平均值。oracle能够保证:在查询成功的前提下,查询结果是一致的。不过,如果数据库管理员没有正确的配置数据库,那么查询就不会成功,就会产生一个常见的oracle错误:“ORA-1555 snapshot too old”。对于旧版本而言,这是一个极难解决的问题,不过对于最近的版本,数据库管理员可以避免这个问题。

3.隔离性

隔离性的原则规定对于其他部分来说,未完成的(也就是未提交的)事务必须不可见。在某个事务进行期间,只有执行该事务的一个会话能看见所做的变更,而其他所有会话看见的都是没有变更的数据(而不是更改后的新值)。这个规定的逻辑含义是:首先,由于整个事务可能没有全部完成(前面介绍了原子性原则以及自动或手动回滚),因此不允许其他用户看到可能反转的变更。其次,在某个事务进行期间,数据时不一致的,也就是说在雇员的薪水发生变化后的短暂时间里,其薪水等级尚未发生相应的变化。事务隔离性要求数据库必须对其他用户隐藏正在进行的事务,这些用户只能看到没有被更新的数据,只有在事务完成时,他们才能看到作为一个一致集合的所有变更。oracle确保事务隔离性:所有会话(除了做出变更的会话之外)都无法看到未提交的数据。未提交数据的读取称为脏读,oracle不允许这样做。

4.持久性

持久性的原则规定一旦某个事务完成,数据库就不可能丢失这个事务。在事务进行期间,隔离性的原则要求除了指定会话涉及的用户之外的任何用户都不能查看当前所做的变更。不过事务一旦完成,所有用户都必须能够立即看到所做的变更,同时数据库必须保证这些变更绝不会丢失(关系数据库不允许丢失数据)。oracle通过使用日志文件在变更完成时记录所有应用于数据的变更向量来满足上述需求。通过将该变更日志应用于以前的备份,就可能在数据库被损坏的情况下恢复所有已完成的工作。当然,数据可能因为用户错误(如不适当的DML、删除或者截断表)而丢失。但是对于oracle和DBA而言,这些事件和其他事物一样,依据持久性原则,它们绝对不可逆。

2.执行SQL语句

1.执行SELECT语句

使用SELECT命令可以检索数据。一条SELECT语句分阶段执行。执行SELECT语句的服务器进程会首先查看包含所需数据的数据块是否已经位于内存和数据库缓冲区缓存中。如果条件成立,那么执行就会立即继续进行。如果条件不成立,那么服务器必须在磁盘上定位相应的数据块,并且将这些数据块复制至数据库缓冲区缓存。
注意:需要牢记,服务器进程将数据块从数据文件读取至数据库缓冲区缓存,而DBWn进程则将数据块从数据库缓冲区缓存写入数据文件。
只要查询所需的数据块位于数据库缓冲区缓存内,那么进一步的处理(如排序或聚合)就在指定会话的PGA中完成。执行完成后,结果集返回至用户进程。
下图显示了SELECT语句的处理方式
                                                      
步骤1将SELECT语句从用户进程传输给服务器进程。服务器将搜索数据库缓冲区缓存,了解必须的块是否已经在内存中,如果在,就进入到步骤4。否则,进入步骤2在数据文件中查找块,然后在步骤3中将它们复制到数据库缓冲区缓存中。步骤4将数据传输到服务器进程,做进一步的处理,此后,步骤5将查询结果返回给用户进程。

2.执行UPDATE语句

对于任何DML操作来说,必须同时处理数据块和撤销块, 并且还会生成重做。ACID测试的A、C和I要求生成撤销,D则要求生成重做。
注意:“撤销”不是“重做”的逆过程。对于表段、索引段或撤销段的数据块的变更来说,重做可以保护所有的数据块变更。就重做而言,撤销段只是另一个段,对这个段所做的任何变更都必须是持久的。
执行DML语句的第一个步骤与执行SELECT语句的第一个步骤相同:必须在数据库缓冲区缓冲中找到所需的数据块或者将所需的数据块从数据文件复制到数据库缓冲区缓存。在这个步骤中,二者唯一的区别是执行DML语句还需要某个撤销段的一个空数据块(或者过期的数据块)。接下来,DML语句的执行就比较复杂。
首先,必须在DML操作影响的所有记录以及相关索引键上放置锁。接下来会生成重做,此时服务器进程在日志缓冲区中写入即将应用于指定数据块的变更向量。这个重做生成操作应用于表块的变更和撤销块的变更。如果要更新某个列,那么这个列的rowid和新值(将要应用于表数据块的变更)以及旧值(将要应用于撤销块的变更)都会被写入日志缓冲区。如果这个列是某个索引键的一部分,那么要应用于该索引的变更以及保护索引变更的撤销块变更也会被写入日志缓冲区。
生成重做后,就可以在数据库缓冲区缓存内完成下列更新操作:使用变更后的列更新表数据块,未变更的列则被写入撤销段数据块。从这一时刻到提交更新操作的时候,与发生变更的记录相关的其他会话中的所有查询都会被重定向至撤销数据。只有执行更新操作的会话才可以在表数据块中看见更新后的行。所有关联的索引变更同样也会应用上述原则。

3.执行INSERT和DELETE语句

从概念上看,INSERT和DELETE语句与UPDATE语句具有相同的管理方式。第一步是在数据库缓冲区缓存中查找相关块,如果它们不在那里,就将它们复制到数据库缓冲区缓存中。
重做生成操作完全相同,应用于数据块和撤销块的所有变更向量都会首先被写入日志缓冲区。对于INSERT,将要应用于表块(还可能包括索引块)的变更向量是构成新行(还可能包括新索引键)的字节。将要应用于撤销块的向量是新行的rowid。对于DELETE,要写入到撤销块的变更向量是整个行。
INSERT和DELETE语句的重要差异在于所生成的撤销数据量不同。插入一条记录时,生成撤销的操作只涉及为撤销块写下新的rowid。这是由于回滚INSERT语句时,oracle只需要rowid信息,因此可以构建如下语句:
[sql] view plain copy
print?
  1. delete from table_name where rowid=rowid_of_the_new_row;  
delete from table_name where rowid=rowid_of_the_new_row;
执行此语句将撤销原始更改。
对于DELETE语句来说,因为整个行(可能多达数KB)都被写入撤销块,因此,可以通过构建完整行插入回表的语句,根据需要回滚这个删除操作。

4.事务的开始和结束

会话开始事务的时间从它发出DML命令时开始。事务持续执行任何数量的DML命令,知道会话发出COMMIT或者ROLLBACK语句为止。只有提交的变更才会变的永久,才会对其他会话可见。不可能嵌套事务,SQL标准不允许用户启动一个事务,然后在终止第一个事务之前再启动另一个事务。使用PL/SQL(Oracle专有的第三代语言)可以这样做,但它不是行业标准的SQL。
显示事务控制语句有COMMIT、ROLLBACK、SAVEPOINT。除了用户发出的COMMIT或者ROLLBACK之外,还有一些情况会隐式终止事务:
1.发出DDL或DCL语句
2.退出用户工具(SQL*PLUS、SQL Developer或者其他工具)
3.客户会话终止
4.系统崩溃
如果用户发出DDL(CREATE、ALTER或DROP)或者DCL(GRANT或REVOKE)命令,就会提交正在处理的事务(如果有的话):会让它变的永久,并且对其他所有用户都可见。这是因为DDL和DCL命令本身都是事务。因为在SQL中不可能嵌套事务,所以如果用户已经在运行事务,就会提交用户已经运行的语句,以及组成DDL和DCL的语句。
如果用户通过发出DML命令来启动事务,然后在没有显示发出COMMIT或者ROLLBACK命令的情况下退出正在使用的工具,事务就会终止——但是使用COMMIT终止还是ROLLBACK终止,则完全取决于如何写工具。许多工具有不同的行为,这取决于退出工具的方式。不管在哪种情况下,都应该是受控制的退出,因此编程人员应该发出COMMIT或ROLLBACK命令。但他们必须进行选择。
如果客户的会话因为某种原因而失败,那么数据库总是会回滚事务。这种失败可能有多种原因:用户进程终止或者在操作系统级别被终止,到数据库服务器的网络连接中断,或者运行该客户工具的计算机崩溃。在所有这些情况中,都没有依次发出COMMIT或ROLLBACK语句,现在轮到数据库来检测发生的情况。行为是终止会话,回滚活动的事务。如果失败发生在服务器端,其行为也是这样。如果数据库服务器因为某种原因崩溃,那么当它下一次启动时,就会回滚来自所有会话的所有之前正在处理的事务。

3.事务控制

oracle的关系数据库范例的实现通过第一个DML语句隐式开始执行某个事务。事务一直延续到COMMIT或ROLLBACK语句执行为止。SAVEPOINT命令不是SQL标准的一部分,实际上是编程人员反向撤销一些语句的简便方式。不需要单独考虑它,它不终止事务。

1.COMMIT命令

许多人(甚至某些经验丰富的DBA)在提交处理这个环节上都会出现不完全或者完全错误的理解oracle体系结构的情况。执行COMMIT命令时发生的所有物理操作时LGWR进程将日志缓冲区的内容刷新到磁盘。DBWn进程完全没有执行任何操作。对于oracle数据库来说,这是一个非常重要的性能特性。
注意:执行COMMIT命令时,DBWn进程不会进行任何操作。 
为了使某个事务持久,所需的全部工作是将组成这个事务的变更写入磁盘(不必使实际表数据存在于磁盘的数据文件上)。如果变更以多重重做日志文件的形式存在于磁盘上,那么在出现使数据库受损的事件时,通过从数据库受损前所做的备份中还原数据文件以及应用重做日志文件中的变更,就可以重新实例化事务。

2.ROLLBACK命令

在事务处理过程中,Oracle会保存事务处理之前数据的映像。在事务处理过程中,会将这个映像提供给查询数据的其他会话。如果出现错误,或者会话故意请求回滚,那么它也可以用来自动回滚事务。
回滚之前数据的状态是,数据已经改变,但反转这些变更所需的信息是可用的。为了满足隔离性原则,会将这些信息提供给其他所有会话。回滚会恢复数据改变之前的映像,从而抛弃所有变更;事务插入的所有行都会删除,事务删除的所有行都会重新插入表中,已经更新的行会回到原始状态。其他会话根本不知道发生了什么,它们绝对看不到这些变更。处理事务的会话现在会将数据看做事务开始之前的数据。

3.SAVEPOINT命令

使用保存点就是允许编程人员在事务中设置一个标记,这个标记可以用来控制ROLLBACK命令的效果。除了回滚整个事务并终止它之外,还可以反转在特定点之后所做的所有变更,同时保持在该点之前所做的变更不变。事务本身继续进行:仍然没有提交,仍然可以回滚,仍然对其他会话不可见。

4.SELECT FOR UPDATE

最后一个事务控制语句是SELECT FOR UPDATE。在默认情况下,Oracle提供最高级别的并发性:读者不打断写者,写者也不打断读者。或者简单的说,一个会话查询另一个会话正在更新的数据,或者一个会话更新另一个会话正在查询的数据,这都没有问题。然而,有时需要改变这种行为,防止改变正在被查询的数据。
应用程序使用SELECT命令检索一组行,将它们提供给用户精读,并给用户提示所做的变更,这种情况不是不常见。因为ORACLE是一个多用户数据库,所以另一个会话也要检索这些行也不是不可能。如果这两个会话都要做出变更,那么会出现一些奇怪的效果。
第一个用户查询一个表:
[sql] view plain copy
print?
  1. select * from emp;  
select * from emp;
这时会出现N条数据,其中包括id=1的数据;
第二个用户删除emp表一条数据
[sql] view plain copy
print?
  1. delete from emp where id=1;  
delete from emp where id=1;
然后第一个用户想修改id=1的数据
[sql] view plain copy
print?
  1. update emp set name=‘test’ where id=1;  
update emp set name='test' where id=1;
此时会出现“0 rows updated”。
这使人感觉有点茫然。解决这个问题的方法之一就是锁定用户感兴趣的行:
[sql] view plain copy
print?
  1. select * from emp for update;  
select * from emp for update;
FOR UPDATE子句会锁定所有检索的行。除了发出命令的会话之外,其他任何会话都不能改变它们,因此后面的更新操作就会成功。这意味着一个会话有一致的数据视图(不会改变),但付出的代价是如果其他会话要更新锁定的行,它们就会挂起(当天他们可以查询)。
在发出命令的会话发出COMMIT或ROLLBACK命令之前,会一直保持FOR UPDATE子句设置的锁定。必须这样来释放锁定,即使没有执行DML命令。

5.所谓的“自动提交”

在结束对提交处理的讨论之前,我们有必要阐明一下经常被提及的“自动提交”(有时候也被称为隐式提交)。您经常会听到这样的说法:Oracle在某些情况下可以进行”自动提交“。执行DDL语句就是其中一种情况,退出某个用户进程(SQL*Plus)则是另一种情况。
”自动提交“纯属子虚乌有。执行某条DDL语句时,实现这个DDL命令的源代码包含了一个完全正规的COMMIT命令。但是,退出用户进程时的情况是怎样呢?如果在WINDOWS终端上使用SQL*Plus并执行一条DML语句,然后再执行”退出“命令,就会提交事务。这是因为SQL*Plus中的”退出“命令嵌入了一条COMMIT语句。但是,单击SQL*Plus窗口的右上角会出现怎样的情况呢?此时,关闭SQL*Plus窗口,如果再次登录SQL*Plus,您会发现已回滚了事务。这是因为为Microsoft Windows编写SQL*Plus的编程人员在关闭SQL*Plus窗口的代码中嵌入了一条ROLLBACK语句。SQL*Plusz在其他平台上的行为可能有所不同,唯一的确认方法是测试。因此,以不同方式退出某个程序时是否能够进行”自动提交“完全取决于编程人员如果编写用户进程。Oracle服务器只是按照指令进行操作。