本文是第七章Transaction部分的读书笔记。
这部分包括的内容为:
- ACID的含义
- 读已提交和它存在的问题
- 快照隔离(可重复读)和它存在的问题
- 可串行化的实现方法
个人感觉这部分的亮点在于对 lost update 和 Write Skew and Phantoms 的分析.
我的总结就是3种情况: read-modify-write, read-compare-update, read-compare-insert
ACID
Atomic
- 没有中间状态,要么成功,要么失败Consistency
- consistency refers to an application-specific notion of the database being in a “good
state". 从应用的角度要求数据库处于正确的状态Isolation
- concurrently executing transactions are isolated from each other.Durability
- once a transaction has committed successfully, any data it has written will not be forgotten
读已提交
- When reading from the database, you will only see data that has been committed (
no dirty reads
). 不存在赃读 - When writing to the database, you will only overwrite data that has been com‐ mitted (
no dirty writes
). 不存在赃写
问题:事务内两次相同的读操作可能得到不同的结果,即不可重复读
快照隔离(可重复读)
原则是读和写之间不冲突,通常用MVCC实现。存在的问题: lost update, Write Skew and Phantoms
问题1: lost update
. 两个线程同时执行read-modify-write
的流程,有一个线程的修改会被另一个线程覆盖掉。例如2个线程同时向一个账户转账200元,转账前账户余额为500元,线程都将更新的值设为700,那么账户的最终余额为700元,丢失了200元
解决
- 使用
原子更新
操作。UPDATE counters SET value = value + 1 WHERE key = 'foo';
- read的时候
显式的加锁
,避免其他线程读 -
自动检测
lost update的发生,如果会发生更新丢失,abort当前transaction。pg的RR有,InnoDB的RR没有 - compare-and-set.
UPDATE wiki_pages SET content = 'new content' WHERE id = 1234 AND content = 'old content';
但要求compare的必须是最新的数据,例如RR隔离级别下的MySQL,content='old content'
是快照读,是读不到最新数据的
问题2: Write Skew and Phantoms
.
发生的情况为read-compare-write
,流程如下
- 1 读数据
- 2 根据读出的数据判断条件是否符合
- 3 如果符合就执行写操作
- 但第3步的写操作会让第2步的条件由符合变为不符合
具体可以分为read-compare-update和read-compare-insert两种情况
-
read-compare-update
. 有三名医生A、B、C,要求任意时刻至少有一名医生是未被预约的。A已被预约,然后来了用户1和2,1看到B和C都可以被预约,满足可预约条件,就预约了B,2同样看到B和C都可以被预约,就预约了C,1和2都执行完后,3名医生都被预约了,就违背了最开始的要求。这种情况可以在read时加锁来解决这个问题,SELECT FOR UPDATE -
read-compare-insert
. 为了保证用户名唯一,为用户分配用户名时,先判断用户名是否存在,如果不存在就插入该用户名,结果就是并发执行的情况下,多个记录有相同的用户名。MySQL的Next-key Lock可以解决这个问题
This effect, where a write in one transaction changes the result of a search query in another transaction, is called a phantom
可串行化
实现方法1: 单线程写。例如Redis
实现方法2: Strong strict two-phase locking(SS2PL)。
InnoDB和SQL Server的可串行化隔离级别就用的该方法实现。这种方法不同于快照隔离要求的对同一条记录的读写不冲突,它是读写互斥
的
- 读的时候要加读锁
- 写的时候要加写锁
- 如果是先读后写,先持有读锁,写的时候变为写锁
- 一旦持有锁,事务结束时才会释放
- 问题:更容易产生死锁,性能比较差
- 加什么锁:为了避免插入幻影记录,加的锁一般都是index-range locking(或称为next-key locking)
实现方法3:Serializable Snapshot Isolation (SSI)。一种乐观
的并发控制技术
- based on snapshot isolation—that is, all reads within a transaction are made from a consistent snapshot of the database. 基于快照隔离
- adds an algorithm for detecting serialization conflicts among writes and determining which transactions to abort. 检测冲突,然后决定哪些事务该abort
参考:
Designing Data-Intensive Applications https://book.douban.com/subject/26197294/