【MySQL】InnoDB在RR隔离级别下当前读与插入的加锁分析
引言
锁分为锁类型和锁模式两类。锁类型包括表锁和行锁,而行锁还细分为记录锁、间隙锁、插入意向锁、Next-Key等更细的子类型。锁模式描述的是加什么锁,包含共享锁、排他锁、意向共享锁、意向排它锁、自增锁。
Server层负责加的锁主要是元数据锁和表锁,InnoDB引擎负责加的锁有:意向锁、行锁、插入意向锁、自增锁。
间隙锁之间不互斥、插入意向锁之间不互斥
当前读
innodb在rr隔离级别下select当前读的加锁原则:
1.根据索引是否唯一、是否等值查询分为四种情况
2.情况允许时临键锁会优化为记录锁和间隙锁,但是非唯一索引范围匹配时,临键锁不会退化
3.原地更新是在更新数据时,直接在页中的记录原始位置修改,无需将整条记录删除后再重新插入。原地更新只有在聚集索引未更新主键且记录长度没有变化时才满足
4.对于非唯一的二级索引,排序不仅根据索引字段,还会根据主键字段
5.如果查询需要回表则在回表时对相应记录加记录锁,如果发生索引覆盖则无需回表加锁
Insert
Insert的顺序是先插入主键索引,再依次插入二级索引。
对于主键索引:
先查找相关page加latch,定位到对应插入位置(<=entry)的record,1.如果record<entry,执行插入流程;
2.如果entry=record说明要插入的entry已经存在,此时接着判断:2.1.如果是INSERT ON DUPLICATE KEY UPDATE,则对record加X临键锁;
2.2.如果是普通INSERT,则对record加S临键锁,接着判断record是否是deleted mark,2.2.1.如果不是delete mark,说明的确有duplicate,需要报错duplicate;
2.2.2.如果是deleted mark,则说明实际没有duplicate record,接着判断record的下一个record上当前有没有锁,如果有的话,则给其加插入意向锁,确保要插入entry的区间没有其他间隙锁和临键锁保护,接着插入entry释放page latch
对于二级索引:
先查找B+tree,加相关page latch,定位到对应插入位置(<=entry)的record,
如果要插入的entry已经存在(entry==record)并且当前index是unique,释放在上一步中对B+tree相关page加的latch,然后重新查找一遍B+tree加相关page latch,定位到>=entry的第一个record。注意,这里中间对B+Tree page释放了latch,所以第二次定位到的record可能会和第一次不同(其他并发事务修改了这里)。
所以这里需要进行判断:
如果record和entry相等:
如果是INSERT ON DUPLICATE KEY UPDATE,则对record加X临键锁,
如果是普通INSERT,则对record加S临键锁,接着判断这个record是否是delete mark,
如果不是delete mark,说明的确有duplicate,释放page latch,返回DB_DUPLICATE_KEY到上层,
如果是delete mark,则说明实际没有duplicate record,获取当前record的下一个record,重新回到判断record和entry是否相等,
如果record和entry不相等:
如果是INSERT ON DUPLICATE KEY UPDATE,则对record加X临键锁,
如果是普通INSERT,则对record加S Gap lock(如果record是SUPREMUM,这里加S Next-key lock)
mtr_commit()释放latch,然后再次查找B+Tree,加相关page latch,定位到entry对应插入位置的record(<=entry)。这里又释放了一次latch然后重新定位,但这次不同之处在于释放了latch之后record还有lock保护,所以不会被其他事务修改。
判断record的下一个record(即next record)上当前有没有锁,如果有的话,则给其加插入意向锁,检查确保要插入entry所在区间没有其他事务Gap lock/Next-key lock保护
插入entry,释放page latch,此时依旧占有lock。
隐式锁是InnoDB实现的一种延迟加锁机制,其特点是只有在可能发生冲突时才加锁,从而减少了锁的数量,提高了系统整体性能。
只有在特殊情况下,才会将隐式锁转换为显示锁。这个转换动作并不是加隐式锁的线程自发去做的,而是其他存在行数据冲突的线程去做的。例如事务1插入记录且未提交,此时事务2尝试对该记录加锁,那么事务2必须先判断记录上保存的事务id是否活跃,如果活跃则帮助事务1建立一个锁对象,而事务2自身进入等待事务1的状态。对于二级索引的隐式锁检测就没有主键索引这么容易了,因为二级索引record没有记录trx_id,只能首先通过其所在page上的max_trx_id与当前活跃事务列表的最小trx_id来比较,小于它的话代表最后一次修改这个page的事务都已经提交,所以record上没有隐式锁,如果大于或等于它的话,就需要回主键找到对应的主键record并遍历undo历史版本来确认是否有隐式锁。
参考一:https://zhuanlan.zhihu.com/p/412358771
参考二:https://xiaolincoding.com/mysql/lock/how_to_lock.html
参考三:https://blog.csdn.net/crazymakercircle/article/details/128888856
浙公网安备 33010602011771号