Oracle数据库隔离级别,特性,问题和解决方法
Oracle数据库隔离级别,特性,问题和解决方法
如果没有任何数据库隔离策略,在多用户(多事务)并发时,会产生下列问题:
- 丢失更新(lost update):两个事务同时更新同一条数据时,会发生更新丢失。
例如:用户A读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户B读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户A把姓名更改为“王小明”(学号=107,姓名=“王小明”,年龄=28)
=> 用户B把年龄更改为33(学号=107,姓名=“小明”,年龄=33)
=> 用户A提交(学号=107,姓名=“王小明”,年龄=28)
=> 用户B提交(学号=107,姓名=“小明”,年龄=33)
用户A对学生姓名的更新丢失了。
- 脏读(dirty read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
例如:用户A读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户A把姓名更改为“王小明”(学号=107,姓名=“王小明”,年龄=28)
=> 用户B读取学号为107的学生(学号=107,姓名=“王小明”,年龄=28)
=> 用户A撤销更改,事务回滚(学号=107,姓名=“小明”,年龄=28)
这样用户B相当于读取了一个从未存在过的数据“王小明”。如果涉及到金额的话问题更为严重,因为用户B读取了一个金额之后,很可能把这个金额与其它金额累加,再把结果保存到汇总数据之中,这样在月底对不上账的时候,由于用户A回滚了事务,数据库内不会有任何操作记录,这样用户B是何时、从哪里读取了错误数据根本无从查起。
- 不可重现的读取(nonrepeatable read):同一查询在同一事务中多次进行,在此期间,由于其他事务提交了对数据的修改或删除,每次返回不同的结果。
例如:假设学生表里只有“小明”和“小丽”2条记录(小明.年龄=20,小丽.年龄=30)。如果用户A把“小明.年龄”更改为40、把“小丽.年龄”更改为50并提交事务。在用户A修改数据并提交前,学生的平均年龄为(20+30)/2=25;在用户A修改数据并提交后,学生的平均年龄为(40+50)/2=45。
现在考虑在用户A更新 2 名学生的年龄时,用户B执行了一个计算平均年龄的事务:
如前所述,在用户A修改数据并提交前,学生的平均年龄为(20+30)/2=25;在用户A修改数据并提交后,学生的平均年龄为(40+50)/2=45。用户B计算得出的平均年龄是 25 或 45 都是可以接受的,但是在上例中用户B计算得出的平均年龄 35 是从未出现在系统中的错误数值。
- 幻读(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,虽然查询条件相同,每次返回的结果集却不同。
事务B使用相同的条件进行了2次查询/筛选,一次是为了向费用结算表插入汇总数据,一次为了确定对费用明细表的更新范围。在这两次筛选之间,事务A提交了一条新的费用明细数据,导致两次筛选的结果不一致。
隔离级别
为避免上述并发问题,ANSI/ISO SQL92标准定义了一些隔离级别:
- 读取未提交数据(read uncommitted)
- 读取已提交数据(read committed)
- 可重现的读取(repeatable read)
- 序列化(serializable)
通过指定不同的隔离级别,可避免上述一种或多种并发问题,见下图。
细心的读者可能已经注意到上图不包括“丢失更新(lost update)”,这是因为“丢失更新”问题需要使用乐观锁或悲观锁来解决,超出本文范围,先不详述。
Oracle 的隔离级别
SQL92定义的隔离级别在理论上很完善,但是 Oracle 显然认为在实际实现的时候并不应该完全照搬SQL92的模型。
- Oracle不支持 SQL92 标准中的“读取未提交数据(read uncommitted)”隔离级别,想要脏读都没可能。
- Oracle 支持 SQL92 标准中的“读取已提交数据(read committed)”隔离级别,(这也是Oracle默认的隔离级别)。
- Oracle不支持 SQL92 标准中的“可重现的读取(repeatable read)”隔离级别,要想避免“不可重现的读取(nonrepeatable read)”可以直接使用“序列化(serializable)”隔离级别。
- Oracle 支持 SQL92 标准中的“序列化(serializable)”隔离级别,但是并不真正阻塞事务的执行(这一点在后文还有详述)。
- Oracle 还另外增加了一个非SQL92标准的“只读(read-only)”隔离级别。
Oracle的序列化(serializable)隔离级别
序列化,顾名思义,是让并发的事务感觉上是一个挨一个地串行执行的。之所以说是“感觉上”,是因为当2个事务并发时,Oracle并不会阻塞其中一个事务去等待另一个事务执行完毕再执行,而是仍然让2个事务同时并行,那么如何能“感觉”是串行的呢?请看下图的实验。
用户B的事务因为指定了serializable隔离级别,所以虽然在查询费用明细表之前,用户A提交了对费用明细表的更改,但是因为用户A提交的更改是在用户B的事务开始之后才提交的,所以这个更改对用户B的事务不可见。也就是说,用户B的事务开始之后,其它事务提交的更改都不会再影响事务内的查询结果,这样感觉上用户A的事务好像是在用户B的事务结束之后才执行的似的。这本来是非常好的一个特性,极大地提高了并行性,但是也会造成问题。
问题1:Oracle的这种“假串行”会让严格依赖于时间的程序产生混乱。
请看下图这个例子,对费用结算的例子稍稍做了一点改动。
程序员的本意是统计2012-3-4这天从零点至运行程序之时的费用总额。如果他以为 Oracle 的 serializable 会像 C# 的 lock 一样阻塞其它事务的话,就会对结果非常吃惊:在2012-3-4 0:00 ~ 2012-3-4 10:02 实际有3条费用明细,总额为20+30+100=150,而不是用户B的事务统计得出的50。
问题2:ORA-08177 Can't serialize access for this transaction (无法序列化访问)错误。
如果你使用了 serialize 隔离级别,没准你的客户会经常抱怨这个随机出现的错误。兄弟,你并不孤独!
导致这个错误的原因有2个:
(1) 两个事务同时更新了同一条数据。你可以这样重现这个错误:事务B开始(使用serialize 隔离级别) => 事务A开始,更新 表1.RowA 但不提交 => 事务B更新表1.RowA,因为行锁定而被阻塞 => 事务A提交 => 事务B报 ORA-08177 错误。
(2) 事务所更新的表的 initrans 参数太小。Oracle 官方文档的说法是,如果使用了 serialize 隔离级别,表的 initrans 参数最小要设置成3(默认是1)。
- 丢失更新(lost update):两个事务同时更新同一条数据时,会发生更新丢失。
例如:用户A读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户B读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户A把姓名更改为“王小明”(学号=107,姓名=“王小明”,年龄=28)
=> 用户B把年龄更改为33(学号=107,姓名=“小明”,年龄=33)
=> 用户A提交(学号=107,姓名=“王小明”,年龄=28)
=> 用户B提交(学号=107,姓名=“小明”,年龄=33)
用户A对学生姓名的更新丢失了。
- 脏读(dirty read):当一个事务读取另一个事务尚未提交的修改时,产生脏读。
例如:用户A读取学号为107的学生(学号=107,姓名=“小明”,年龄=28)
=> 用户A把姓名更改为“王小明”(学号=107,姓名=“王小明”,年龄=28)
=> 用户B读取学号为107的学生(学号=107,姓名=“王小明”,年龄=28)
=> 用户A撤销更改,事务回滚(学号=107,姓名=“小明”,年龄=28)
这样用户B相当于读取了一个从未存在过的数据“王小明”。如果涉及到金额的话问题更为严重,因为用户B读取了一个金额之后,很可能把这个金额与其它金额累加,再把结果保存到汇总数据之中,这样在月底对不上账的时候,由于用户A回滚了事务,数据库内不会有任何操作记录,这样用户B是何时、从哪里读取了错误数据根本无从查起。
- 不可重现的读取(nonrepeatable read):同一查询在同一事务中多次进行,在此期间,由于其他事务提交了对数据的修改或删除,每次返回不同的结果。
例如:假设学生表里只有“小明”和“小丽”2条记录(小明.年龄=20,小丽.年龄=30)。如果用户A把“小明.年龄”更改为40、把“小丽.年龄”更改为50并提交事务。在用户A修改数据并提交前,学生的平均年龄为(20+30)/2=25;在用户A修改数据并提交后,学生的平均年龄为(40+50)/2=45。
现在考虑在用户A更新 2 名学生的年龄时,用户B执行了一个计算平均年龄的事务:
如前所述,在用户A修改数据并提交前,学生的平均年龄为(20+30)/2=25;在用户A修改数据并提交后,学生的平均年龄为(40+50)/2=45。用户B计算得出的平均年龄是 25 或 45 都是可以接受的,但是在上例中用户B计算得出的平均年龄 35 是从未出现在系统中的错误数值。
- 幻读(phantom read):同一查询在同一事务中多次进行,由于其他提交事务所做的插入操作,虽然查询条件相同,每次返回的结果集却不同。
事务B使用相同的条件进行了2次查询/筛选,一次是为了向费用结算表插入汇总数据,一次为了确定对费用明细表的更新范围。在这两次筛选之间,事务A提交了一条新的费用明细数据,导致两次筛选的结果不一致。
隔离级别
为避免上述并发问题,ANSI/ISO SQL92标准定义了一些隔离级别:
- 读取未提交数据(read uncommitted)
- 读取已提交数据(read committed)
- 可重现的读取(repeatable read)
- 序列化(serializable)
通过指定不同的隔离级别,可避免上述一种或多种并发问题,见下图。
细心的读者可能已经注意到上图不包括“丢失更新(lost update)”,这是因为“丢失更新”问题需要使用乐观锁或悲观锁来解决,超出本文范围,先不详述。
Oracle 的隔离级别
SQL92定义的隔离级别在理论上很完善,但是 Oracle 显然认为在实际实现的时候并不应该完全照搬SQL92的模型。
- Oracle不支持 SQL92 标准中的“读取未提交数据(read uncommitted)”隔离级别,想要脏读都没可能。
- Oracle 支持 SQL92 标准中的“读取已提交数据(read committed)”隔离级别,(这也是Oracle默认的隔离级别)。
- Oracle不支持 SQL92 标准中的“可重现的读取(repeatable read)”隔离级别,要想避免“不可重现的读取(nonrepeatable read)”可以直接使用“序列化(serializable)”隔离级别。
- Oracle 支持 SQL92 标准中的“序列化(serializable)”隔离级别,但是并不真正阻塞事务的执行(这一点在后文还有详述)。
- Oracle 还另外增加了一个非SQL92标准的“只读(read-only)”隔离级别。
Oracle的序列化(serializable)隔离级别
序列化,顾名思义,是让并发的事务感觉上是一个挨一个地串行执行的。之所以说是“感觉上”,是因为当2个事务并发时,Oracle并不会阻塞其中一个事务去等待另一个事务执行完毕再执行,而是仍然让2个事务同时并行,那么如何能“感觉”是串行的呢?请看下图的实验。
用户B的事务因为指定了serializable隔离级别,所以虽然在查询费用明细表之前,用户A提交了对费用明细表的更改,但是因为用户A提交的更改是在用户B的事务开始之后才提交的,所以这个更改对用户B的事务不可见。也就是说,用户B的事务开始之后,其它事务提交的更改都不会再影响事务内的查询结果,这样感觉上用户A的事务好像是在用户B的事务结束之后才执行的似的。这本来是非常好的一个特性,极大地提高了并行性,但是也会造成问题。
问题1:Oracle的这种“假串行”会让严格依赖于时间的程序产生混乱。
请看下图这个例子,对费用结算的例子稍稍做了一点改动。
程序员的本意是统计2012-3-4这天从零点至运行程序之时的费用总额。如果他以为 Oracle 的 serializable 会像 C# 的 lock 一样阻塞其它事务的话,就会对结果非常吃惊:在2012-3-4 0:00 ~ 2012-3-4 10:02 实际有3条费用明细,总额为20+30+100=150,而不是用户B的事务统计得出的50。
问题2:ORA-08177 Can't serialize access for this transaction (无法序列化访问)错误。
如果你使用了 serialize 隔离级别,没准你的客户会经常抱怨这个随机出现的错误。兄弟,你并不孤独!
导致这个错误的原因有2个:
(1) 两个事务同时更新了同一条数据。你可以这样重现这个错误:事务B开始(使用serialize 隔离级别) => 事务A开始,更新 表1.RowA 但不提交 => 事务B更新表1.RowA,因为行锁定而被阻塞 => 事务A提交 => 事务B报 ORA-08177 错误。
(2) 事务所更新的表的 initrans 参数太小。Oracle 官方文档的说法是,如果使用了 serialize 隔离级别,表的 initrans 参数最小要设置成3(默认是1)。
alter table 费用明细表 initrans 3;
原文:“Oracle Database stores control information in each data block to manage access by concurrent transactions. Therefore, if you set the transaction isolation level to SERIALIZABLE, then you must use the ALTER TABLE command to set INITRANS to at least 3. This parameter causes Oracle Database to allocate sufficient storage in each block to record the history of recent transactions that accessed the block. Higher values should be used for tables that will undergo many transactions updating the same blocks.”
注意,人家说的是“最小是3”。我用自己笔记本里的 32 位 Oracle10g 测试的结果是设置成 3 也会频繁地报 ORA-08177 错误。后来改成5 和 10,都不行。改成50,终于不报错了。但是都说了这个错误是随机的,有时候3也没问题的——反过来说,设置成50也未必保险。坑爹啊!真坑爹!!这就像菜谱里面写的“放入适量的油……”,他喵的到底多少算是“适量”啊?!!!
有兴趣的读者可以使用下图的语句实际测试一下。
我的建议是,还是尽量不要用 serialize 隔离级别吧,用户是不会理解什么叫“无法序列化访问”的,他只会觉得你的“XX功能会随机地不好用”倒是真的。稍后我们再简单讨论一下不用 serialize 隔离级别如何避免幻读。现在先来看一下 Oracle 官方文档建议的适合使用 serialize 隔离级别的3种情况。
(1) With large databases and short transactions that update only a fewrows(大数据库、只更新几条数据的短事务)
(2) Where the chance that two concurrent transactions will modify thesame rows is relatively low(2个并发事务更新同一条数据的几率不大)
(3) Where relatively long-running transactions are primarily read only(相对运行时间较长的事务主要用来读取数据)
使用默认的 read committed 隔离级别,如何避免幻读产生的问题
使用默认的 rea
注意,人家说的是“最小是3”。我用自己笔记本里的 32 位 Oracle10g 测试的结果是设置成 3 也会频繁地报 ORA-08177 错误。后来改成5 和 10,都不行。改成50,终于不报错了。但是都说了这个错误是随机的,有时候3也没问题的——反过来说,设置成50也未必保险。坑爹啊!真坑爹!!这就像菜谱里面写的“放入适量的油……”,他喵的到底多少算是“适量”啊?!!!
有兴趣的读者可以使用下图的语句实际测试一下。
我的建议是,还是尽量不要用 serialize 隔离级别吧,用户是不会理解什么叫“无法序列化访问”的,他只会觉得你的“XX功能会随机地不好用”倒是真的。稍后我们再简单讨论一下不用 serialize 隔离级别如何避免幻读。现在先来看一下 Oracle 官方文档建议的适合使用 serialize 隔离级别的3种情况。
(1) With large databases and short transactions that update only a fewrows(大数据库、只更新几条数据的短事务)
(2) Where the chance that two concurrent transactions will modify thesame rows is relatively low(2个并发事务更新同一条数据的几率不大)
(3) Where relatively long-running transactions are primarily read only(相对运行时间较长的事务主要用来读取数据)
使用默认的 read committed 隔离级别,如何避免幻读产生的问题
使用默认的 rea
- 更多Oracle疑问解答:
- 运行exp备份oracle数据库提示oracle-12154错误
- 有没有,生产Oracle Rman 备份脚本的工具啊!
- 初学orcle,希望有大大帮忙解说一下详细步骤,从登录oracle到创建表的过程
- oracle语句问题:一张user表,三个字段,id,name,time,插入记录比如:张三2007,李四2008,张三2011
- 如何写一个ORACLE触发器同步两个表中的数据?
- oracle 如何查看一个服务器上有多少个数据库.
- oracle 创建包的时候错误 求解
- oracle 重复列的问题
- oracle 中如何查处2星期前的数据
- 请教oracle数据库安装中的问题
- 请问谁能提供给我标准的oracle ERP的数据库表结构并详细说明各表主要的作用?
- 安装oracle遇到的问题 invalid entry CRC (expected 0x3e12e795 but got 0x9db0e9fd)
- 我的是ORACLE 10G,在RMAN中如何按指定的时间恢复数据文件啊?
- oracle为什么没有自动增长列
- oracle快捷键都有哪些啊?