ACID
我们经常使用ACID(Atomicity, Consistency, Isolation, Durability)来描述数据系统的事务提供的安全保障,与之相对的是BASE(Basically Available, Soft state, Eventully consistency)系统。
Atomicity
原子性(Atomicity)指的是一个操作不能被分成更小的片段。也就是说,一组事务中的操作是数据系统处理的一个最小的原子单元。这些操作要么全部成功提交,要么全部失败,被数据系统丢弃,不会存在部分操作成功、部分操作失败的问题。如果没有原子性的保障,一个事务中的多组写操作执行过程中失败之后,开发人员很难正确处理这种异常问题。
Consistency
一致性(Consistency)指的是数据系统中的不变量(invariants)永远为真。例如记账系统中,贷款方和收款方的记录总是相同的。一致性并不是数据系统的固有的特性,数据系统并不会阻止业务系统写入错误的数据。相反,一致性依赖于业务系统对事务的正确使用,应用程序必须正确使用原子性和隔离性来维持系统内部的一致性。
Isolation
隔离性(Isolation)指的是并发执行的多个事务之间是互相隔离,互不影响的。传统意义上的隔离性指的是可串行的(serializability),也就是说每个正在执行的事务都可以假设不存在其它正在执行的事务,最终数据库的结果也和这些事务串行执行(run serially)的结果一样。然而,因为serializability会导致数据系统性能严重下降,在生产系统中一般会使用其它更弱的隔离级别来提高系统的性能。
Durability
持久性(Durability)指的是数据系统保障数据安全的能力。事务一旦成功提交后,即使出现硬件错误或者数据库崩溃,写入的数据也不应丢失。对于单节点的数据系统,持久性的实现一般是指写入write-ahead日志,这样在数据库文件损坏时可以使用日志进行恢复。在多副本数据系统中,持久性一般指数据被成功写入且被同步到一定数量的节点上,这样在单一节点故障时,仍能保障数据安全。
Weak Isolation problems
数据库中因并发事务导致的问题主要有如下几种,参考论文A Critique of ANSI SQL Isolation Levels》中的记法,w1[x] 表示事务1写x, r2[x]表示事务2读x,c1/c2分别表示事务1和事务2的提交。
Dirty Write
事务1修改一个数据,事务2在事务1提交或者回滚之前修改同一条数据,脏写会引发应用程序破坏一致性。
考虑一个需要维持x=y为系统内部不变量的应用程序,如果有如下一组操作,w1[x] w2[x] w2[y] c2 w1[y] c1. 事务1对y和事务2对x的修改都被系统记录下来。如果事务1试图将x和y写为2,事务2试图将x和y写为1,那么在允许脏写出现的数据系统中,最终结果为x = 2 y = 1,破坏了系统的一致性。而在完全可串行的系统中, 最终结果要么是x=y=2,要么是x=y=1。
Dirty Read
事务1修改一条数据,事务2在事务1提交或者回滚之前,读到这条未提交的数据。如果之后事务1回滚,那么事务2读到了一条从未提交过的数据。
Non-repeatable Read
事务1读取一条数据,事务2读取修改或者删除此数据并提交,事务1再次读取此数据。在Read Committed的隔离级别下,事务1第二次读取到的是事务2修改后的数据,在同一个事务中,读到的两组不同的数据,也就是不可重复读。
Read skew
事务1读x,事务2写x, y 提交,事务1读y。在读已提交的隔离级别下,事务1读到的x是事务2修改之前的值,读到的y是事务2修改之后的值,如果x和y需要满足某种约束,此时读到的数据可能破坏了系统的一致性。
考虑一个需要维持x=y为系统内部不变量的应用程序,如果有如下一组操作,r1[x] w2[x] w2[y] c2 r1[y],假定初始状态x=y=1,事务2尝试将x y写为2。事务1读到的x=1 y=2,破坏了系统的一致性。
Non-repeatable Read是Read skew在x=y时的一种特殊形式。
Lost Update
事务1读x,事务2写x,事务1写x,事务1提交,此时,事务2的提交丢失。
例如,初始状态为x=1,事务1和事务2都要对x进行加1操作。事务1读x,事务2读x=1,事务2写x=2,事务2提交,事务1写x=2,事务1提交。此时事务2对x的修改被事务1覆盖,更新丢失。在可串行假设下,最终x应为3。
避免lost update的方法有:
-
atomic write
update exampleTable set x = x+1 where key = 'key'
-
explicit lock
select x from exampleTable where key = 'key' for update
-
compare and set (add version column?)
update exampleTable set x = 2 where key = 'key' and x = 1
Phantom
事务1读取一组满足条件P的数据,事务2插入/删除一个满足条件P的数据,并提交。如果事务1再次进行读取,读到的满足条件P的数据与第一次读取到的数据不同。
Write Skew
事务1读x,事务2读y,事务1写y,事务2写x,事务1或事务2提交。
考虑一个需要维持x + y > 0 的系统,初始状态为x=y=1,事务1尝试将y置为0,事务2尝试将x置为0,由于Write Skew的存在,最终系统的状态为x = y =0,不再满足x+y > 0的约束。而在可串行系统中,最后的结果是x + y = 1。
一般的,由Phantom引发Write Skew的步骤是:
- 一条select语句,用于查看是否有满足条件的行
- 根据步骤1的结果,程序决定是否进行步骤3
- 进行写操作(insert, update, delete),写操作的结果影响步骤2的预设条件。如果此时提交写事务,再进行步骤1的查询,得到的满足条件的行记录和上一次事务结果不同。
Summary
问题 | 时序 | highest exist level |
---|---|---|
Dirty Write | w1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order- | - |
Dirty Read | w1[x]...r2[x]...(a1 and c2 in either order) | Read Uncommitted |
Non-repeatable Read | r1[x]...w2[x]...((c1 or a1) and (c2 or a2) in any order) | Read Committed |
Read skew | r1[x]...w2[x]...w2[y]...c2...r1[y]...(c1 or a1) | Read Committed |
Write Skew | r1[x]...r2[y]...w1[y]...w2[x]...(c1 and c2 occur) | Repeatable Read |
Lost Updates | r1[x]...w2[x]...w1[x]...c1 | Repeatable Read |
Phantoms | r1[P]...w2[y in P]...((c1 or a1) and (c2 or a2) any order) | Repeatable Read |
关联业务问题及处理
-
计数取号程序(防止lost update)
-
多用户同时抢占某一资源(事务1更新记录x,事务2更新记录y)
user1 user2 select count(1) from tableA where ...
select count(1) from tableA where ...
if (res > 0) update tableB set ... where user = 'user1'
if (res > 0) update tableB set ... where user = 'user2'
-
注册用户名(事务1插入x,事务2插入y)
user1 user2 select count(1) from tableA where ...
select count(1) from tableA where ...
if (res = 0) insert into userTable... user = 'user1'
if (res = 0) insert into userTable user = 'user2'
-
用户账户余额限制(事务1插入item1,事务2插入item2)
trans1 trans2 select money from tableA where ...
select money from tableA where ...
if (money > item1.cost) insert into itemTable...
if (money > item2.cost) insert into itemTable...
网友评论