Oracle数据库提供三种隔离级别,分别是:
- Read Committed Isolation Level
- Serializable Isolation Level
- Read-Only Isolation Level
Read Committed Isolation Level
该隔离级别是默认的,事务执行的每个查询只看到查询之前已提交的数据,而不是事务开始时提交的数据。这种隔离级别适用于几乎不可能发生事务冲突的数据库环境。
一个read committed事务的查询避免读取在查询正在进行时提交的数据,然而,由于数据库不能阻止其他事务修改查询所读取的数据,其他事务可能在查询执行之间进行数据的更改,因此,运行同一个查询两次的事务可能会出现不可重复读和幻读。
Read Consistency in the Read Committed Isolation Level
为每个查询提供一致的结果集,保证数据的一致性,无需用户采取任何行动。隐式查询,如update语句中where字句隐含的查询,保证了一致的结果集。然而,隐式查询的每个语句都不会看到DML语句本身所做的更改,但是看到了更改之前存在的数据。
如果select列表包含了pl/sql函数,那么数据库在pl/sql函数代码内部运行SQL的语句级别应用语句级别的读一致性,而不是在父SQL级别。例如,函数可以访问一张表,其数据已经被另一个用户更改和提交。对于函数中select的每次执行,将会创建一个新的读一致性快照。
Conflicting Writes in Read Committed Transaction
在一个读取已提交的事务中,当事务尝试更改被另一个未提交的并发事务修改的行(有时成为阻塞事务)时,就会发生冲突写。读取已提交事务等待正发生阻塞的事务结束和释放它的行锁。选项如下:
- 如果阻塞事务回滚,则等待事务将更改先前锁定的行,就像其他事务不存在;
- 如果阻塞事务提交并释放它的锁,则等待事务将执行其预期的更新到新更改的行。
下面将演示在读取已提交事务中冲突写和丢失更新:
会话1 | 会话2 | 说明 |
---|---|---|
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 1600 | 会话1查询SMITH、ALLEN和LUCY的工资,LUCY员工不存在。 | |
SQL> update emp set sal=5000 2 where ename=’SMITH’; 1 row updated. | 会话1 开启一个事务,更新SMITH的工资,事务隔离级别默认为Read Committed; | |
SQL> set transaction isolation level READ COMMITTED; Transaction set. | 会话2开启事务,并设置默认隔离级别为Read Committed | |
SQL> select ename,sal from emp where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 1600 | 会话2查询SMITH、ALLEN和LUCY的工资,会话1的事务未提交前Oracle数据库使用读一致性显示SMITH的工资; | |
SQL> update emp set sal=3000 2 where ename=’ALLEN’; 1 row updated. | 会话2成功更新ALLEN的工资,因为事务1使用行级锁仅仅锁定了SMITH行; | |
SQL> insert into emp(empno,ename,job,deptno) values 2 (7935,’LUCY’,’CLERK’,20); 1 row created. | 会话1插入一行,员工为LUCY; | |
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 3000 | 会话2查询,只看到本会话已修改的数据; | |
SQL> update emp set sal=5500 2 where ename=’SMITH’; | 修改SMITH的工资,该行已被事务1锁定,创建了冲突写,因此事务2只能等待,直到事务1结束; | |
SQL> commit; Commit complete. | 事务1提交,并结束事务; | |
1 row updated. | SMITH行的锁被释放,因此可以继续处理对SMITH的修改; | |
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY SMITH 5500 ALLEN 3000 | 事务2查询SMITH、ALLEN和LUCY的工资,LUCY是由事务1插入的,事务2可以看到,对于SMITH的工资,事务2只能看到它自己所做的修改; | |
SQL> commit; Commit complete. | 事务1提交,并结束事务; | |
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY SMITH 5500 ALLEN 3000 | 会话1查询SMITH、ALLEN和LUCY的工资,SMITH的工资是5500,是事务2更新的,而不是事务1更新的值5000,事务1 的更新丢失了。这就是丢失更新。 |
Serializable Isolation Level
在串行化隔离级别,事务只能看到事务开始时(而不是查询开始时)已提交的更改和事务本身所做的更改。在一个环境运行的串行化事务使它看起来好像没有其他用户修改数据库中的数据。
串行化隔离适用于下面场景:
- 大型数据库和仅更新几行的短事务;
- 两个并发事务将要修改同一行记录的可能性较低的地方;
- 相对运行时间较长的事务主要是只读的地方;
在串行化隔离中,从语句级别获得读一致性通常扩展到整个事务。在重新读取时,被事务读取的任何行确保是相同的。任何查询都保证在事务期间返回相同的结果集,因此,其他事务所做的更改对于查询来说是不可见的,不管它运行了多长时间。串行化查询不会出现脏读、不可重复读和幻读。
Oracle数据库允许一个串行化事务更改一行,前提条件是,只有当串行化事务开始时,被其他事务所做的行的更改已经提交。当串行化事务试图更新或删除在串行化事务开始后提交的由不同事物更改的数据时,数据库会产生一个错误:
ORA-08177: Cannot serialize access for this transaction
当出现ORA-08177时,应用程序可采取如下措施:
- 提交已执行的工作到该时点;
- 执行其他语句,可能回滚到事务中之前创建的savepoint;
- 回滚整个事务。
下面将演示串行化事务:
会话1 | 会话2 | 说明 |
---|---|---|
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 1600 | | 会话1查询SMITH、ALLEN和LUCY的工资,LUCY员工不存在。 |
SQL> update emp set sal=5000 2 where ename=’SMITH’; 1 row updated. | | 会话1 开启一个事务,更新SMITH的工资,事务隔离级别默认为Read Committed; |
| SQL> set transaction isolation level serializable; Transaction set. | 会话2开启一个事务,并设置隔离级别为串行化; |
| SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 1600 | 会话2查询SMITH、ALLEN和LUCY的工资,会话1的事务未提交前Oracle数据库使用读一致性显示SMITH的工资; |
| SQL> update emp set sal=3000 2 where ename=’ALLEN’; 1 row updated. | 更新ALLEN的工资为3000; |
SQL> insert into emp(empno,ename,job,deptno) values 2 (7935,’LUCY’,’CLERK’,20); 1 row created. | | 事务1插入一行员工为LUCY记录; |
SQL> commit; Commit complete. | | 事务1提交,结束事务; |
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY SMITH 5000 ALLEN 1600 | SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- SMITH 3439.86 ALLEN 3000 | 会话1查询SMITH、ALLEN和LUCY的工资,但看不到事务2中的未提交的更新;事务2查询SMITH、ALLEN和LUCY的工资,Oracle数据库的读一致性确保插入的LUCY和事务1修改的SMITH的值对事务2不可见,事务2 只能看到它自己对ALLEN的更新; |
| SQL> commit; Commit complete. | 事务2提交,结束事务; |
SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY SMITH 5000 ALLEN 3000 | SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY SMITH 5000 ALLEN 3000 | 会话1和2查询SMITH、ALLEN和LUCY的工资,每个会话都看到事务1和事务2所有提交的更改; |
SQL> update emp set sal=9000 2 where ename=’LUCY’; 1 row updated. | | 会话1开启事务3更新LUCY的工资,事务3的默认隔离级别是Read Committed; |
| | |
| SQL> set transaction isolation level serializable; Transaction set. | 会话2开启事务4,设置隔离级别为Serializable; |
| SQL> update emp set sal=9500 2 where ename=’LUCY’; | 事务4更新LUCY的工资,但已被事务3锁定,故出现等待; |
SQL> commit; Commit complete. | | 事务3提交并结束事务; |
| SQL> update emp set sal=9500 2 where ename=’LUCY’; update emp set sal=9500 * ERROR at line 1: ORA-08177: can’t serialize access for this transaction | 事务3提交导致事务4出现ORA-08177错误,因为事务3在事务4开始后提交更新; |
| SQL> rollback; Rollback complete. | 会话2回滚事务4,并结束事务; |
| SQL> set transaction isolation level serializable; Transaction set. | 会话2开启事务5,设置隔离级别为Serializable; |
| SQL> select ename,sal from emp 2 where ename in(‘SMITH’,’ALLEN’,’LUCY’); ENAME SAL ———- ———- LUCY 9000 SMITH 5000 ALLEN 3000 | 事务5查询SMITH、ALLEN和LUCY的工资,事务3提交的LUCY的工资对事务5是可见的; |
| SQL> update emp set sal=9500 2 where ename=’LUCY’; 1 row updated. | 事务5更新LUCY的工资,因为事务3在事务5开始前提交,因此避免了Serializable访问; |
| SQL> commit; Commit complete. | 会话2提交更新,并结束事务; |
Read-Only Isolation Level
只读隔离级别和串行化隔离级别相似,但是只读事务不允许在事务中修改数据,除非是SYS用户。因此,只读事务不会受到ORA-08177的影响。只读事务用户生成报告,它的内容必须与事务开始的时间相一致。
Oracle数据库通过根据需要从undo segment重构数据来实现读一致性。由于undo segment以循环方式使用,数据库会覆盖undo数据,对于运行时间较长的报告可能会有快照过旧的风险(snapshot too old),因为需要用来实现读一致性的undo数据被其他事务重用。对于快照过旧的问题,可以通过设置undo retention period参数来避免,该参数表示undo数据被覆盖之前,undo数据保留的最小时间。
参考:官方文档