如果说数据在 ACID 特性(带有了并发控制技术)的保护下会发生不一致的现象,那么:
在 ACID 和快照隔离级别技术(多版本)的保护下,是不是数据就一定不再会产生不一致的现象呢 ?
答案是否定的。数据库系统中数据的异常,在多种并发控制技术中已经被解决,但这不表明所有的异常都已经被解决,更不表明不再有新的异常被发现。
我们知道,数据库并发控制技术中有一个大名鼎鼎的技术,称为快照隔离( Snapshot Isolation ),这项技术解决了读写冲突,在保证数据不会产生前面两节提到的读异常和写异常的情况下,使得读写互不阻塞(两阶段锁技术读写操作互相阻塞),提高了并发度。
注意我们这里谈到的多版本是“ multi-version ,简称 MV ”,其相对于“ single-valued ,简称 SV ”,这个多版本是快照隔离并发控制技术中的数据项有多个版本,其含义仅此而已。快照隔离并发技术是( Multiversion concurrency control , MVCC )技术的一种,两者都要基于“数据项存在多个版本”,但与多版本并发控制技术相比两者是有区别的(具体区别参见 2.2 节对并发控制技术的探讨)。
快照隔离并发控制技术的缺点,是并不能真正保证事务为“可序列化的”,即事务间的并发操作依旧会引发数据异常现象,但是这里的数据异常现象区别于前面提到的各种异常现象,其异常现象是“业务的逻辑语义”引发的,即除了抽象的读写操作,数据间还应该满足一定语义,即约束( constraint )。
在快照隔离并发控制技术中并发的事务因不满足约束而发生的异常,成为“写偏序( Write Skew )”,这样的异常有两种,参见 1- 4 。
表 1-4 写偏序异常的两种情况 [1]
两个事务写偏序 | 三个事务写偏序 | ||||
T1 | T2 | T1 | T2 | T3 | |
t0 | x ← SELECT COUNT( ? ) FROM doctors WHERE on ? call = true | x ← SELECT current_batch | |||
t1 | x ← SELECT COUNT( ? ) FROM doctors WHERE on ? call = true | INCREMENT | |||
t2 | IF x ≥ 2 THEN UPDATE doctors SET on ? call = false WHERE name = Alice | Commit | |||
t3 | IF x ≥ 2 THEN UPDATE doctors SET on ? call = false WHERE name = Bob | x ← SELECT current_batch | |||
t4 | Commit |
SELECT SUM(amount) FROM WHERE batch = x ? 1 | |||
t5 | Commit | COMMIT | |||
t6 | INSERT INTO receipts | ||||
t7 | COMMIT |
说明:
q 表格头两行,表明写偏序异常现象的两种情况,分别是由两个事务引发异常、三个事务引发异常。
q 表格第一列,时间值列,表明时间值在逐渐增长,即 t0<t1<t2<t3<t4<t5<t6<t7 。
q 对于每一种异常现象,都分为 2 个列,分别是两个并发的事务,各自命名为 T1 事务和 T2 事务。
q 对于二个事务引发的异常现象(简单写偏序, Simple Write Skew ): 按照时间顺序, T1 事务在 t0 时刻读取了在打电话的值班医生个数, T2 事务在 t1 时刻也读取了在打电话的值班医生个数。事务 T1 在 t2 时刻进行判断:如果在打电话的值班医生个数大于等于 2 人则请 Alice 停止打电话。事务 T2 在 t3 时刻进行判断:如果在打电话的值班医生个数大于等于 2 人则请 Bob 停止打电话。然后事务 T1 和 T2 分别提交。如果在这种并发的情况下,允许事务 T1 和 T2 都提交成功,则 t6 时刻, Alice 和 Bob 都停止了打电话。如果串行执行事务,先执行事务 T1 后执行事务 T2 , Alice 会停止打电话但 Bob 不会停止,这与前一种情况的结果不同;如果先执行事务 T2 后执行事务 T1 , Bob 会停止打电话但 Alice 不会停止,这与前一种情况的结果也不同;这表明前一种并发执行是非序列化的,即事务 T1 、 T2 并发时违反了约束( 约束为 :如果同时打电话的人数大于等于 2 人则请 Alice 或 Bob 其中一个人停止打电话直到同时打电话的人数少于 2 人)发生了写偏序异常现象。对于简单写偏序,可以用一个形象化的图表示,参见图 1-1 。
q 对于三个事务引发的异常现象( Batch Processing ): 对于这种情况,后两个并发更新事务 T3 和 T2 是可串行化的且不存在任何异常,但是一个只读事务 T1 出现在某个时刻却可能正好造成问题。所出现的问题是这样的,当事务 T3 提交时, T2 处于活跃状态,这时,事务 T1 启动要读取事务 T2 和 T3 涉及的数据( current_batch 和 receipts ),这时,事务 T1 的快照包括了事务 T3 的插入后的结果(因为 T3 已经提交);但是,事务 T2 没有提交,它的插入操作数据不包含在事务 T1 的快照中。在优先图(如图 1-2 )中会造成一个环(有关如何形成这样的环), 说明这样的 调度是非可串行化的 。
图 1-1 两个事务引发的异常现象优先图
图 1-2 三个事务引发的异常现象优先图
本节所述的这两种情况,如果使用优先图表示,都可以在参与操作的事务之间,画出一个环,存在环说明: 调度是非可串行化的。 为解决这样的问题,这就要求数据库引擎必须在事务提交时而不是在快照上检查完整性约束以避免本节所述的不一致现象。
注意:
更多的写偏序异常示例,可以参见: https://wiki.postgresql.org/wiki/SSI#Read_Only_Transactions
[1] 示例源自论文: Dan R. K. Ports , Kevin Grittner , Serializable Snapshot Isolation in PostgreSQL
备注:转载自:http://www.tuicool.com/articles/bEBRzyM
转载于:https://blog.51cto.com/jiaojusuimu/1874831