MySQL把读操作分为两大类:锁定读和非锁定读(即locking read和nonlocking read),所谓非锁定读就是不对表添加事务锁的读操作,如Repeatable Read和Read Committed隔离级别下的select语句(可能脏读也算?)。MySQL的一致性非锁定读是通过MVCC机制实现的。锁定读是指添加事务锁的读操作,例如select for update和select lock in share mode语句。
官网参考:这里
关于MySQL的锁机制和事务隔离级别,参考以下两篇博客:
https://www.linuxidc.com/Linux/2018-04/151914.htm
https://www.linuxidc.com/Linux/2018-04/151923.htm
第一部分:概述
锁定读、update和delete,这些操作通常会在扫描到的索引记录上添加record locks,InnoDB不关心这些行是否会被where条件过滤,因为InnoDB不记得具体的where条件,它只知道哪个索引范围被扫描过。
这些锁定添加的锁通常是next-key lock,这种锁既锁定扫描到的索引记录,也锁定索引间的gap。不过gap锁可以被显示的禁用,参考https://www.linuxidc.com/Linux/2018-04/151914.htm的Gap lock部分。
如果在SQL执行时你需要对次级索引记录加X模式的行锁,那么InnoDB也会检索相应的主键索引并加锁。
如果执行的SQL找不到合适的索引,InnoDB不得不去进行全表扫描,那么InnoDB会把表的每一个聚集索引记录都锁住,这可以看作是表级锁,同样参考https://www.linuxidc.com/Linux/2018-04/151923.htm的表锁部分。这种全表锁定会导致其他事务无法插入和更改(同样参考链接中的表锁兼容性部分),因此为SQL创建合适的索引是很有必要的,因为表锁(非意向锁)会导致DML操作阻塞。
对于select...for update和select...lock in share mode这种锁定读来说,开始时InnoDB会锁定所有扫描的索引记录,但是最后会释放那些不符合条件的索引记录上的锁(例如被where语句过滤掉的行)。但是在某些情况下由于结果行与源表的联系丢失,导致这些行锁不会被释放,例如:union操作,被扫描的中间结果行会被插入到一个临时表中以便形成最终的结果集,在这种情况下锁定行与原表之间的联系丢失,那么剩余的扫描行直到整个SQL执行结束才会被释放(不是事务执行结束)。
第二部分:InnoDB中SQL语句的加锁类型
1.在Repeatable Read和Read Committed事物隔离级别下,SELECT ... FROM语句是一种一致性非锁定读。而在SERIALIZABLE隔离级别下是锁定读,会在扫描的索引记录范围内添加Next-key行锁,不过如果扫描的是唯一索引,那么只会添加Record lock。
2.SELECT ... FROM ... LOCK IN SHARE MODE在扫描到的索引记录上添加S模式的Next-key行锁,同样的如果扫描的是唯一索引,那么只会添加S模式的Record lock。
3.SELECT ... FROM ... FOR UPDATE在扫描到的索引记录上添加X模式的Next-key行锁,同样的如果扫描的是唯一索引,那么只会添加X模式的Record lock。
4.UPDATE ... WHERE ...语句会在扫描到的所有记录上添加X模式的next-key lock(即便被删的行不存在),同样的如果扫描的是唯一索引,那么只会添加X模式的Record lock。
5.当UPDATE语句修改的是主键索引时,InnoDB会隐式的将所有的次级索引锁定(二级索引都是用主键做书签的,因此修改主键索引是很耗资源的操作)。在插入二级索引记录或者为插入二级索引做重复性检查扫描时(unique index),update也会把受影响的二级索引锁定。
6.DELETE FROM ... WHERE ...语句会在扫描到的所有记录上添加X模式的next-key lock(即便被删的行不存在),同样的如果扫描的是唯一索引,那么只会添加X模式的Record lock。
7.INSERT语句会在插入的行上添加Record lock,Insert语句不会阻止其他事务在同一个gap上插入行。