Record Lock
翻译成记录锁,是加载索引记录上的锁。
例如:select c1 from t where c1 = 10 for update 会对c1 = 10这条记录加锁,为了防止其他事务插入,更新或删除c1=10的行。
记录锁锁的是索引记录,即使表没有定义索引,InnoDB 也会创建一个隐藏的聚集索引,并使用这个索引来锁定记录。
gap Lock
翻译成间隙锁,指的是在索引记录之间的间隙上的锁。或者在第一个索引记录之前或最后一个索引之后的间隙上的锁。
Gap指的是InnoDB的索引数据结构中可以插入新值的问题。
当你用select …. for update 锁定一组行时,InnoDB 可以创建锁,应用于索引中的实际值和他们的间隙。例如,当你选择所有大于10的值进行更新,间隙锁将阻止另外一个事务插入大于10的新值。
既然是锁,那么就会影响到数据库的并发性。所以,间隙锁只有在RR隔离级别才起作用。
在RR隔离级别下,对于锁定的读操作(select for update, lock in share mode),update 操作,delete操作,会进行如下的加锁:
- 对于具有唯一搜索条件的唯一索引,InnoDB只锁定找到的索引记录,不会锁定间隙。
对于其他搜索条件,InnoDB会锁定扫描的索引范围,使用gap lock或next-key lock来阻塞其他事务插入范围覆盖的间隙。
Next-key lock
Next-key lock是索引记录上的记录锁和索引记录之间的间隙上的间隙锁的组合。
假如一个索引包含值10,11,13,20,此索引可能的next-key锁包含以下区间: (-无穷,10] (10, 11] (11,13] (13,20] (20, 正无穷] 对于最后一个间隙,无穷并不是一个真正的索引记录,因此,实际上,这个next-key锁只锁定最大索引值之后的间隙。
Next-key的锁范围都是左开右闭的。
-
RR能解决幻读
网上很多介绍,RR能解决不可重复读,但是不能解决幻读的问题,只有Serilizable能解决。其实,这种想法是不对的。
因为Mysql跟标准的RR不一定,标准的RR确实存在幻读的问题。但是InnoDB中的RR是通过next-key lock解决了幻读的问题的。
因为有了next-key-lock,所以需要加行锁的时候,会同时在索引的间隙中加锁,这就使得其他事务无法在这些间隙中插入记录,这就解决了幻读的问题。mysql的加锁原则
两个原则:
加锁的基本单位是next-key lock,是一个前开后闭区间
- 查找的过程中访问到的对象才枷锁
优化:
- 索引上的等值查询,给唯一索引加锁的时候,next-key lock退化成行锁
- 索引上的等值查询,向右遍历时且最后一个值不满足等值查询的时候,next-key lock退化成间隙锁。
一个bug:
-
举例
数据库有以下记录:
普通索引c 0 5 10 15 20 25
执行select id from t where c = 5 lock in share mode
- 根据原则1,加锁的基本单位是next-key lock, 因此会给(0, 5]加上next-key lock。要注意,c是普通索引,因为仅访问c = 5 这一条记录是不能马上停下来的,需要向右遍历,查到c = 10才放弃
- 根据原则2,访问到的数据都要加锁,因此也要给(5,10】加next-key lock
- 根据优化2,等值判断,向右遍历,最后一个值c=5这个等值条件,因为退化成间隙锁(5,10】
- 根据原则2,只有访问到的数据才加锁,这个查询会覆盖索引,并不需要访问主键索引,所以主键所以上没有加任何锁
执行select * from t where c >=10 and c < 11 for update
- 根据原则1,加锁的单位是next-key lock, 会给(5,10】加next-key lock,范围查找就会往后继续找,找到id = 15这一行停下来
- 根据原则2, 访问到的都要加锁,因此需要加next-key lock (10,15]
- 由于索引C不是唯一索引,没有优化原则,也就是不会退化成行锁,因此最后加锁的是,索引c上的(5,10】(10,15】两个next-key lock