写在前面:
之前一直被MySQL的各种锁弄得很迷,不同的文章写的都不太一样。然后读了一些文章,终于能分清为什么他们说的锁不一样了。其实关键在于MySQL的体系结构,mysql锁——元数据锁和innodb锁 这篇文章说了“但由于MySQL是Server-Engine架构,所以MDL锁是在Server中实现”,因此有部分锁是在Server中实现的,而我们这篇文章只说InnoDB层的锁。
InnoDB的锁在MySQL的官方文档 InnoDB Locking里只有下面截图里的几种,下边我们分别来介绍。
InnoDB implements standard row-level locking where there are two types of locks, shared (S) locks and exclusive (X) locks。
- A shared (S) lock permits the transaction that holds the lock to read a row.
- An exclusive (X) lock permits the transaction that holds the lock to update or delete a row.
这段话明确了共享锁/排他锁都只是行锁,与间隙锁无关,这一点很重要,后面还会强调这一点。其中:
意向锁
意向共享锁/意向排他锁属于表锁,且取得意向共享锁/意向排他锁是取得共享锁/排他锁的前置条件。
如果没有「意向锁」,那么加「独占表锁」时,就需要遍历表里所有记录,查看是否有记录存在独占锁,这样效率会很慢。
那么有了「意向锁」,由于在对记录加独占锁前,先会加上表级别的意向独占锁,那么在加「独占表锁」时,直接查该表是否有意向独占锁,如果有就意味着表里已经有记录被加了独占锁,这样就不用去遍历表里的记录。
所以,意向锁的目的是为了快速判断表里是否有记录被加锁。
如果一个事务请求的锁模式与当前的锁兼容,InnoDB就请求的锁授予该事务;反之,如果两者不兼容,该事务就要等待锁释放。
意向锁是InnoDB自动加的,不需用户干预。对于UPDATE、DELETE和INSERT语句,InnoDB会自动给涉及的数据集加排他锁(X);对于普通SELECT语句,InnoDB会自动给涉及数据集加共享锁(S);事务可以通过以下语句显式给记录集加共享锁或排锁。
共享锁(S):SELECT * FROM table_name WHERE … LOCK IN SHARE MODE
排他锁(X):SELECT * FROM table_name WHERE … FOR UPDATE
A record lock is a lock on an index record. Record locks always lock index records, even if a table is defined with no indexes. For such cases, InnoDB creates a hidden clustered index and uses this index for record locking.
记录锁一定是作用在索引上的,没有索引就生成一个隐藏索引。
A gap lock is a lock on a gap between index records, or a lock on the gap before the first or after the last index record。
间隙锁就是锁住索引之间的记录,或者锁住第一个索引前或者最后一个索引后的记录。
间隙锁一定是开区间。
在MySQL官网上还有一段非常关键的描述:
Gap locks in InnoDB are “purely inhibitive”, which means that their only purpose is to prevent other transactions from inserting to the gap. Gap locks can co-exist. A gap lock taken by one transaction does not prevent another transaction from taking a gap lock on the same gap. There is no difference between shared and exclusive gap locks.They do not conflict with each other, and they perform the same function.
这段话表明间隙锁在本质上是不区分共享间隙锁或互斥间隙锁的,而且间隙锁是不互斥的,即两个事务可以同时持有包含共同间隙的间隙锁。这里的共同间隙包括两种场景:其一是两个间隙锁的间隙区间完全一样;其二是一个间隙锁包含的间隙区间是另一个间隙锁包含间隙区间的子集。间隙锁本质上是用于阻止其他事务在该间隙内插入新记录,而自身事务是允许在该间隙内插入数据的。也就是说间隙锁的应用场景包括并发读取、并发更新、并发删除和并发插入。
在MySQL的官方文档中有以下描述:
A next-key lock is a combination of a record lock on the index record and a gap lock on the gap before the index record.
这句话表明临键锁是行锁+间隙锁,即临键锁是是一个左开右闭的区间,比如(3,5]。
在MySQL的官方文档中有以下重要描述:
An insert intention lock is a type of gap lock set by INSERT operations prior to row insertion. This lock signals the intent to insert in such a way that multiple transactions inserting into the same index gap need not wait for each other if they are not inserting at the same position within the gap. Suppose that there are index records with values of 4 and 7. Separate transactions that attempt to insert values of 5 and 6, respectively, each lock the gap between 4 and 7 with insert intention locks prior to obtaining the exclusive lock on the inserted row, but do not block each other because the rows are nonconflicting.
这段话表明插入意向锁是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。如果说间隙锁锁住的是一个区间,那么插入意向锁锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。与间隙锁的另一个非常重要的差别是:尽管插入意向锁也属于间隙锁,但两个事务却不能在同一时间内一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。这里我们再回顾一下共享锁和排他锁:共享锁用于读取操作,而排他锁是用于更新或删除操作。也就是说插入意向锁、共享锁和排他锁涵盖了常用的增删改查四个动作。
自增锁是一种特殊的表级锁,主要用于事务中插入自增字段,也就是我们最常用的自增主键id。通过innodb_autoinc_lock_mode参数可以设置自增主键的生成策略。
空间索引的Predicate Locks,在RR级别或者 SERIALIZABLE 下,Next-key 锁无法有效保证空间索引的正常,所以产生此锁。
参考链接:
15.7.1 InnoDB Locking【官方文档】
MySQL InnoDB 存储引擎原理浅析
InnoDB Locking——锁类型简介
Mysql数据库中的各种锁
MySQL 有哪些锁?
MySQL常见的七种锁详细介绍【推荐阅读,但是关于MVCC与锁的部分好像有点问题,可以先跳过】