MySQL 事务隔离级别的实现原理
知识储备
只有 InnoDB 支持事务, 所以这里说的事务隔离级别是指 InnoDB 下的事务隔离级别
隔离级别
读未提交: 一个事务可以读取到另一个事务未提交的修改. 这会带来脏读, 幻读, 不可重复读问题
读已提交: 一个事务只能读取另一个事务已经提交的修改. 其避免了脏读, 仍然存在不可以重复读和幻读问题
可重复读: 同一个事务中多次读取相同的数据返回的结果是一样的. 其避免了脏读和不可重复读问题, 但是幻读依然存在
串行化: 事务串行之行. 避免了以上所有问题
以上是 SQL-92 标准中定义的四种隔离级别. 在 MySQL 中, 默认的隔离级别是 REPEATABLE-READ(可重复读), 并且解决了幻读问题.
不可重复读重点在于 Update 和 delete, 而幻读的重点在于 insert
MVCC
MVCC 的全称是多版本并发控制. MVCC 使得 InnoDB 的事务隔离级别下执行一致性读操作有了保证. 简单说就是为了查询一些正在被另一个事务更新的行, 并且可以看到它们被更新之前的值. 这是一个用来增强并发性的强大技术, 可以使得查询不用等待另一个事务释放锁.
MVCC 会给每一行增加三个字段. 分别是 DB-TRX-ID DB-ROLL-PTR,DB-ROW-ID
增删查改
在 InnoDB 中, 给每行增加两个隐藏字段来实现 MVCC, 一个用来记录数据行的创建时间, 另一个用来记录行的过期时间, 在实际操作中, 存储的并不是时间, 而是事务版本号, 每开启一个新事务, 事务的版本号就会递增. 所以增删改查中对版本号的作用如下:
select:
读取创建版本小于或等于当前事务版本号, 并且删除版本为空或大于当前事务版本的记录. 这样可以保证在读取之前记录都是存在的
insert:
将当前事务的版本号保存至行的创建版本号
update
新插入一行, 并以当前事务版本号作为新行的创建版本号, 同时将原记录行的删除版本号设置为当前事务版本号
delete
将当前事务版本号保存至行的删除版本号
快照读和当前读
快照读: 读取的是快照版本, 也就是历史版本
当前读: 读取的是最新版版
普通的 select 就是快照读, 而 update,delete,insert,select...LOCK In SHARE MODE,SELECT...for update 就是当前读
一致性非锁定读和锁定读
锁定读
在一个事务中, 标准的 SELECT 语句是不会加锁, 但是有两种情况例外. SELECT ... LOCK IN SHARE MODE 和 SELECT ... FOR UPDATE.
SELECT ... LOCK IN SHARE MODE: 给记录假设共享锁, 这样其他事务职能读不能修改, 直到当前事务提交
SELECT ... FOR UPDATE: 给索引记录加锁, 这种情况跟 UPDATE 的加锁情况是一样的
一致性非锁定读
consistent read(一致性读),InnoDB 用多版本来提供查询数据库在某个时间点的快照. 如果隔离级别是 REPEATABLE READ, 那么在同一个事务中的所有一致性读都读的是事务中第一个的读读到的快照; 如果是 READ COMMITTED, 那么一个事务中的每一个一致性读都会读到它自己刷新的快照版本. Consistent read(一致性读) 是 READ COMMITTED 和 REPEATABLE READ 隔离级别下普通 SELECT 语句默认的模式. 一致性读不会给它锁访问的表加任何形式的锁, 因此其他事务可以同时并发的修改它们
锁
Record Locks(记录锁): 在索引记录上加锁
Gap Locks(间隙锁): 在索引记录之间加锁, 或者在第一个索引记录之前加锁, 或者在最后一个索引记录之后加锁
Next-Key Locks: 在索引记录上加锁, 并且在索引记录之前的间隙加锁. 相当于 Record Locks 与 Gap Locks 的一个结合
假如一个索引包含以下几个值: 10,11,13,20. 那么这个索引的 next-key 锁将会覆盖以下区间:
- (negative infinity, 10]
- (10, 11]
- (11, 13]
- (13, 20]
- (20, positive infinity)
理论分析
在默认的隔离级别中, 普通的 SELECT 用的是一致性读不加锁. 而对于锁定读, UPDATE 和 DELETE, 则需要加锁, 至于加什么锁是有不同情况的. 如果对一个唯一索引使用了唯一的检索条件, 那么只需要锁定相应的索引记录就好; 如果是没有使用唯一索引作为检索条件, 或者用到了索引范围扫描, 那么将会使用间隙锁或者 next-key 锁来以此阻塞其他会话向这个范围内的间隙插入数据
利用 MVCC 实现一致性非锁定读, 保证在同一个事务中多次读取相同的数据返回的结果是一样的, 解决了不可重复读问题
利用 Gap Locks 和 Next-key 可以阻止其他事务在锁定区间内插入数据, 解决了幻读问题
综上所述, MySQL 的默认隔离级别的实现依赖于 MVCC 和锁, 准确点说就是一致性读和锁
实例分析
客户端 A
客户端 A 开始一个事务, 并以主键唯一索引作为检索条件进行更新
客户端 B
客户端 B 开始一个事务, 由于客户端 A 已经开始了事务并以主键索引作为检索条件, 所以会造成该索引被锁定. 其他索引以及其他范围则不会被锁定可以正常操作
客户端 A
当客户端 A 重新开始一个事务并没有使用唯一索引作为检索条件
客户端 B
客户端 B 开始一个事务, 由于客户端 A 的事务操作, 那么 MySQL 会使用 next-key 和间隙锁以此阻塞其他会话对表的操作
来源: https://www.cnblogs.com/develop-SZT/p/10339138.html