InnoDB 加锁流程浅析

加锁流程浅析

InnoDB 的加锁过程比较复杂,将所有扫描到的记录都加锁,范围查询会加间隙锁,然后加锁过程按照两阶段锁 2PL 来实现,也就是先加锁,然后所有的锁在事物提交的时候释放。加锁的策略会和数据库的隔离级别有关,在默认的可重复读的隔离级别的情况下,加锁的流程还会和查询条件中是否包含索引,是主键索引还是普通索引,是否是唯一索引等有关。

譬如对于 select * from o_order where order_sn = '201912102322' for update; 这条 SQL 语句,在不同的索引情况下其加锁策略也不一致:

  • order_sn 是主键索引,这种情况将在主键索引上的 order_sn = 201912102322 这条记录上加排他锁。

  • order_sn 是普通索引,并且是唯一索引,将会对普通索引上对应的一条记录加排他锁,对主键索引上对应的记录加排他锁。

  • order_sn 是普通索引,并且不是唯一索引,将会对普通索引上 order_sn = 201912102322 一条或者多条记录加锁,并且对这些记录对应的主键索引上的记录加锁。这里除了加上行锁外,还会加上间隙锁,防止其他事务插入 order_sn = 201912102322 的记录,然而如果是唯一索引就不需要间隙锁,行锁就可以。

  • order_sn 上没有索引,innoDB 将会在主键索引上全表扫描,这里并没有加表锁,而是将所有的记录都会加上行级排他锁,而实际上 innoDB 内部做了优化,当扫描到一行记录后发现不匹配就会把锁给释放,当然这个违背了 2PL 原则在事务提交的时候释放。这里除了对记录进行加锁,还会对每两个记录之间的间隙加锁,所以最终将会保存所有的间隙锁和 order_sn = 201912102322 的行锁。

  • order_sn = 201912102322 这条记录不存在的情况下,如果 order_sn 是主键索引,则会加一个间隙锁,而这个间隙是主键索引中 order_sn 小于 201912102322 的第一条记录到大于 201912102322 的第一条记录。试想一下如果不加间隙锁,如果其他事物插入了一条 order_sn = 201912102322 的记录,由于 select for update 是当前读,即使上面那个事物没有提交,如果在该事物中重新查询一次就会发生幻读。

  • 如果没有索引,则对扫描到的所有记录和间隙都加锁,如果不匹配行锁将会释放只剩下间隙锁。回忆一下上面讲的数据页的结果中又一个最大记录和最小记录,Infimum 和 Supremum Record,这两个记录在加间隙锁的时候就会用到。

下一页