前言
随着业务迭代,我们会更多地面临分布式环境带来的挑战。
数据库软件或硬件出现问题(写过程中)
应用程序崩溃(多个读写操作中)
网络延迟异常导致数据库链接
并发读写问题
多个客户端之间的竞争条件下出现的异常
事务的引入简化了上述问题的解决,但需要我们正确运用,因此理解事务就变得尤为重要,本文旨在对事务隔离级别简单说明。
相关理论
ACID:Atomicity、Consistency、Isolation、Durability
CAP:Consistency、Availability、Partition tolerance
BASE:Basically Available、Soft state、Eventually consistency
请参考:【读书笔记】分布式系统理论
事务隔离级别
未提交读(Read Uncommited)
脏读:一个事务读到了另一个事务未提交的数据
已提交读(Read Commited)
读数据:读已经提交的数据(没有脏读)
-
没有解决不可重复读(read skew, nonrepeatable read) 问题,如下图
1位置Alice查询
select balance from accounts where id = 1
2位置Alice认为
Account1 + Account2 = 900
,但是她总共有1000!
即使两个并发事务全部提交,Alice依然能看到正确的数据,但这“暂时的不一致”(temporary inconsistency)也是不可接受的。
- 为了解决上述脏读、不可重复读的问题,快照隔离(snapshot isolation)技术被广泛应用于关系型数据库存储引擎。
快照隔离与可重复读(Snapshot Isolation and Repeatable Read)
- 快照隔离简单来讲就是每个事务都去读取一致的快照(已提交的) ,也就是1的时间节点Alice会查到数据500。
- 实现快照隔离的方式就是多版本并发控制(MVCC, multi-version concurrency control)。
- 如果在Read commited级别,MVCC只需要维护(已提交版本、未提交版本)
新增created by
与deleted by
,记录每个数据快照的创建事务ID与删除事务ID(update可以认为是delete & insert)。
一个小坑:快照隔离在不同的数据库有不同的实现命名,《Designing Data Intensive Applications》原文如下:
In Oracle it is called serializable, and in PostgreSQL and MySQL it is called repeatable read
- 为了更加清楚地说明,下文就用可重复读(Repeatable Read)级别代替快照隔离
幻读问题
提交读与可重复读对只读(read-only)事务支持我们已经讨论过了,那么对写事务,特别是并发的两个写事务之间会出现什么问题呢?
可以看到,Alice事务和Bob事务分别对shift_id=1234 and on_call=true
的数据进行了筛选,并且分别对数据进行了更新,导致最后的数据出现了错误。
幻读问题特征
- 使用SELECT语句使用特定条件查询出多条数据;
- 依赖上个SELECT语句的结果,决定接下来的操作;
- 决定后,写数据并提交事务。
核心在于,B事务插入或删除了,满足A事务SELECT查询条件的数据,导致A事务出现”幻觉“。
串行化(Serializability)
针对上述幻读场景,需要更强的事务隔离级别约束,就是串行化。
串行化级别的实现,取决于各个数据库存储引擎的选型,但主要有以下三种:
- 串行化运行
- 2PL(Two Phase Locking)机制
- 乐观并发控制,例如串行化快照隔离
InnoDB如何解决”幻读“
InnoDB采用2PL机制解决”幻读“,并且在可重复读(RR) 级别实现,具体实现方式为next-key locking
。
2PL简介
多个并发事务允许读取相同数据,仅当这个数据没有在写入中:
A事务已经读取数据,B事务并发地写入该数据,B必须等A提交或者中断后执行;
A事务已经写入数据,B事务并发的读取该数据,B必须等A提交或者中断后执行。
- 这里的”数据“包含范围查询出的区间
NEXT-KEY
next-key锁是一种行锁与gap锁的结合,gap锁可以对索引值范围内的行数据进行加锁。
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
如果我们拥有一个索引,并且值为10, 11, 13, 20;那么,next-key锁可能的加锁范围如下所示。
(negative infinity, 10]
(10, 11]
(11, 13]
(13, 20]
(20, positive infinity)
结合Bob和Alice的例子,Bob已经读取shift_id=1234 and on_call=true
的行数据,那么next-key会对shift_id=1234
的范围数据进行加锁,Alice写入必须等待Bob提交完成或者中断,避免幻读问题产生。
参考
- Designing Data Intensive Applications
- tech.meituan.com/2014/08/20/…
- dtm.pub/guide/start…
- dev.mysql.com/doc/refman/…