一、 InnoDB锁分类
MySQL官方文档中将锁分为7类,7类之中根据锁粒度分其实只有两类——行锁和表锁。
为什么要有行锁和表锁?
- 锁定粒度:表锁 > 行锁
- 加锁效率:表锁 > 行锁
- 冲突概率:表锁 > 行锁
- 并发性能:表锁 < 行锁
1. 行锁类型包括
- 共享锁和排他锁(Shared and Exclusive Locks)
- 记录锁(Record Locks)
- 间隙锁(Gap Locks)
- 记录+间隙组合锁,即Next-Key锁(Next-Key Locks)
- 插入意向锁(Insert Intention Locks)
2. 表锁类型包括
- 意向锁(Intention Locks)
- 自增锁(AUTO-INC Locks)
二、 行锁
1. 共享锁和排他锁(Shared and Exclusive Locks)
共享锁和排他锁,从名字就可以看出其实是两类锁:
- 共享锁 S:持有该锁,可以读取行
- 排他锁 X:持有该锁可以delete或者update行
- S锁与X锁相互冲突
需要注意由于MySQL有MVCC特性,普通查询不会加行锁,属于非锁定读,锁定读才会加行锁:
- 加S锁:select ... for share(8.0新增),select ... lock in share mode(8.0之前)
- 加X锁:select ... for update
2. 记录锁(Record Locks)
所谓的“记录”指的是索引记录,因此记录锁都是加在索引上的。如果表上没有索引,会使用是隐藏的聚集索引。当一条 SQL 没有走任何索引时,会在每一条聚集索引后面加 X 锁,这个类似于表锁,但原理上和表锁是不同的。
3. 间隙锁(Gap Locks)
gap锁的锁定范围是索引记录间的gap、第一条或最后一条索引记录之前的gap。
它是为了解决幻读问题而生的,因此一般需要在“可重复读”级别及以上才会有,防止insert记录产生幻读,不让别的事务在间隙插入值,update操作不会申请gap锁。RC级别通常没有gap锁(外键检测和duplicate key检测除外)。
gap锁可以跨越单个索引值,多个索引值,甚至是空值。在通过唯一索引进行唯一查找时,不会用到gap锁。但是有情况例外,就是唯一索引是多列组合索引。注意,gap的X锁和另外一个事务在同一个gap上的S锁是兼容的。因为如果某个记录从索引中删除时,这条记录上的gap锁(多个事务持有的)一定会被合并。
gap锁在InnoDB中是专一功能(purely inhibitive),这意味着它们只能防止其他事务在这个间隙中插入数据,而无法阻止不同的事务在同样的间隙上获取间隙锁。所以就间隙锁来说,S锁和X锁效果一样。
4. 记录+间隙组合锁 Next-Key锁(Next-Key Locks)
锁如其名,Next-Key锁其实就是记录锁+间隙锁。锁定范围是索引记录本身+之前的gap(左开右闭)。它发生在查询过程中,如果没有主键,则会对辅助索引下一个键值加上gap lock。也有S和X两种模式。
next-key lock还会加在“supremum pseudo-record”上,什么是supremum pseudo-record呢?它是索引中的伪记录(pseudo-record),代表此索引中可能存在的最大值。也就是会锁上当前索引到最大字到正无穷大。比如:
select* from mytab where col_a>=10 for update;
就锁定了10到正无穷到所有值,此时无法 insert into mytable(col_a) values (11); ,需要等待select释放锁。
这里需要注意:
- 当查询唯一索引时,InnoDB会对Next-Key Lock进行优化,将其降级为Record Lock,仅锁住索引本身,而不是范围。
- InnoDB存储引擎还会对辅助索引下一个键值加上gap lock。
5. 插入意向锁(Insert Intention Locks)
针对insert操作的特殊间隙锁,主要是为提高insert并发能力。插入具有相同索引间隙的多个事务,如果插入值不同,不需要相互等待。插入意向锁之间相互不冲突。
三、 表锁
1. 意向锁(Intention Locks)
MySQL中的意向锁是一种表锁,表示将来要对表添加什么类型的锁,类型有两种:
- 意向共享锁 IS:事务将要对表上的行加S锁,那么在表上就会加上IS锁。
- 意向独占锁 IX:事务将要对表上的行加X锁,那么在表上就会加上IX锁。
意向锁协议遵循如下规则:
- 在一个事务在表的行上申请S锁之前,它必须在表上获得IS锁或者IX锁。
- 在一个事务在表的行上申请X锁之前,它必须在表上获得IX锁。
意向锁与共享锁和排他锁的兼容性表格如下:
可以看出:
- 排它锁(X):与任何锁都不兼容
- 共享锁(S):只兼容共享锁和意向共享锁
- 意向锁(IS,IX): 互相兼容,行级别的锁只兼容共享锁
2. 自增锁(AUTO-INC Locks)
自增锁是一种特殊的表锁,在事务insert带有AUTO_INCREMENT字段时获取。最简单的例子是,一个事务在insert表时,其他所有insert该表的事务都要等待,这样才能保证后面事务插入的序列值是连续的。
根据insert的数据是否可以推测,分成以下几类:
- Simple inserts
这种insert可以预估行数,它是单行或者多行的INSERT 或 REPLACE 语句(不包含嵌套子查询),另外 INSERT … ON DUPLICATE KEY UPDATE也不包含在此类型内。
- Bulk inserts
这种insert行数是不可预估的。它通常是INSERT … SELECT, REPLACE … SELECT, 和 LOAD DATA,innodb假定每次处理每行数据,AUTO_INCREMENT都是一个新的值。
- Mixed-mode inserts
是simple-insert,但是部分auto increment值给定部分不给定。如在c1是自增值的情况下,INSERT INTO t1 (c1,c2) VALUES (1,'a'), (NULL,'b'), (5,'c'), (NULL,'d'); 如INSERT … ON DUPLICATE KEY UPDATE,
自增锁的行为由参数innodb_autoinc_lock_mode控制,参数可选值为 0,1,2:
0 (traditional lock mode):表示上述所有类型的insert都基于传统的lock模式,即一个session插入,所有session等待。
1 (consecutive lock mode):
- 对于Simple inserts,使用轻量级锁,只要获取了相应的auto increment就释放锁,不会等到语句结束
- 对于bulk inserts,会产生一个特殊的AUTO-INC table-level lock,直到语句结束(这里是语句结束就释放锁,而不是事务结束才释放)
2 (interleaved lock mode),进行bulk insert的时候,不会产生table级别的自增锁,允许其他insert插入。
四、 锁模式对应含义
当使用 show engine innodb 查看锁信息时,会看到lock_mode字段,下面列出其含义:
- IX:意向排他锁
- X:代表Next-Key Lock锁定记录本身及之前的间隙(X)
- S:代表Next-Key Lock锁定记录本身及之前的间隙(S)
- X,REC_NOT_GAP:代表只锁定记录本身(X)
- S,REC_NOT_GAP:代表只锁定记录本身(S)
- X,GAP:间隙锁,不锁定记录本身(X)
- S,GAP:间隙锁,不锁定记录本身(S)
- X,GAP,INSERT_INTENTION:插入意向锁
参考
https://oracleblog.org/study-note/about-mysql-lock-and-transaction/
wikipedia – Isolation (database systems)
https://dev.mysql.com/doc/refman/5.7/en/innodb-locking.html
https://www.dazhuanlan.com/2019/12/09/5dedacf4da52f/
https://blog.csdn.net/xiaoweite1/article/details/102649464
《MySQL 性能优化金字塔法则》