前言
上篇文章讲到了MySQL的RR隔离级别通过MVCC+Next-key Locks解决幻读问题,下面就给大家仔细讲讲这两个机制究竟是什么。
MVCC(多版本并发控制)
Mysql的大多数事务型存储引擎实现都不是简单的行级锁。基于提升并发性考虑,一般都同时实现了多版本并发控制(MVCC),包括Oracle、PostgreSQL。不过实现各不相同。
MVCC的实现是通过保存数据在某一个时间点快照来实现的。也就是说不管实现时间多长,每个事物看到的数据都是一致的。
分为乐观(optimistic)并发控制和悲观(pressimistic)并发控制。
MVCC是如何工作的:
版本号
隐藏的列
- 创建版本号:创建一行数据时,将当前系统版本号作为创建版本号赋值。
- 删除版本号:删除一行数据时,将当前系统版本号作为删除版本号赋值。如果该快照的删除版本号大于当前事务版本号表示该快照有效,否则表示该快照已经被删除了。
REPEATABLE READ(可重复读)隔离级别下MVCC如何工作:
1. SELECT
1. InnoDB只查找版本早于当前事务版本的数据行,这样可以确保事务读取的行要么是在开始事务之前已经存在要么是事务自身插入或者修改过的,在事务开始之后才插入的行,事务不会看到。
2. INSERT
3. DELETE
4. UPDATE
保存这两个版本号,使大多数操作都不用加锁。使数据操作简单,性能很好,并且能保证只会读取到复合要求的行。不足之处是每行记录都需要额外的存储空间,需要做更多的行检查工作和一些额外的维护工作。
MVCC只在COMMITTED READ(读提交)和REPEATABLE READ(可重复读)两种隔离级别下工作。
可以认为MVCC是行级锁一个变种,但是他很多情况下避免了加锁操作,开销更低。虽然不同数据库的实现机制有所不同,但大都实现了非阻塞的读操作(读不用加锁,且能避免出现不可重复读和幻读),写操作也只锁定必要的行(写必须加锁,否则不同事务并发写会导致数据不一致)。
快照读与当前读
1. 快照读
使用 MVCC 读取的是快照中的数据,这样可以减少加锁所带来的开销。
2. 当前读
读取的是最新的数据,需要加锁。以下第一个语句需要加 S 锁,其它都需要加 X 锁。
select * from table where ? lock in share mode;
select * from table where ? for update;
insert;
update;
delete;复制代码
如何解决幻读
- 使用串行化读的隔离级别
- MVCC+next-key locks:next-key locks由record locks(索引加锁) 和 gap locks(间隙锁,每次锁住的不光是需要使用的数据,还会锁住这些数据附近的数据)
InnoDB有三种行锁的算法:
Record Locks
Gap Locks
SELECT c FROM t WHERE c BETWEEN 10 and 20 FOR UPDATE;
Next-Key Locks
MVCC 不能解决幻读的问题,Next-Key Locks 就是为了解决这个问题而存在的。在可重复读(REPEATABLE READ)隔离级别下,使用 MVCC + Next-Key Locks 可以解决幻读问题。
它是 Record Locks 和 Gap Locks 的结合,不仅锁定一个记录上的索引,也锁定索引之间的间隙。
CREATE TABLE `test` (
`id` int(11) primary key auto_increment,
`xid` int, KEY `xid` (`xid`) )
ENGINE=InnoDB DEFAULT CHARSET=utf8;
insert into test(xid) values (1), (3), (5), (8), (11);复制代码
看下图就明白了:
该SQL语句锁定的范围是(5,8],下个键值范围是(8,11],所以插入5~11之间的值的时候都会被锁定,要求等待。即:插入5,6,7,8,9,10 会被锁住。插入非这个范围内的值都正常。
小结
本篇文章总结了MVCC和InnoDB下的三种行锁的算法,这些知识属于MySQL的原理层面,有了这方面的认识后,在以后对MySQL的使用也能更加得心应手,不过我个人而言对于上面最后一个问题为什么xid为11时并不会被阻塞那里还有一点点不理解,参考的别人博客给出的解释是id是自增的,innodb的B+树是有序的,所以并不会阻塞后面的插入。此解释还有待我回去翻看一下《mysq技术内幕》中对于next-key locks的详细实现的描述再来做出更合理的解释。