[精选]详细介绍MySQL中常见的锁
一、死锁示例
考虑下面一个MySQL死锁的示例:
有如下一张表:
CREATE TABLE `test` (
`id` int(20) NOT NULL,
`name` varchar(20) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
表中有如下数据:
mysql> SELECT * FROM test;
+----+------+
| id | name |
+----+------+
| 1 | 1 |
| 5 | 5 |
| 10 | 10 |
| 15 | 15 |
| 20 | 20 |
| 25 | 25 |
+----+------+
6 rows in set (0.00 sec)
当数据库的隔离级别为Repeatable Read或Serializable时,我们来看这样的两个并发事务(场景一):
上面两个并发事务一定会发生死锁(这里之所以限定RR和Serializable两个隔离级别,是因为只有这两个级别下才会有间隙锁/临键锁,而这是导致死锁的根本原因,后面会详细分析)。
我们再来看另外一个并发场景(场景二):
在这个并发场景下,两个事务均能成功提交,而不会有死锁。
二、MySQL的七种锁
行锁(Record Locks)
间隙锁(Gap Locks)
临键锁(Next-key Locks)
共享锁/排他锁(Shared and Exclusive Locks)
意向共享锁/意向排他锁(Intention Shared and Exclusive Locks)
插入意向锁(Insert Intention Locks)
自增锁(Auto-inc Locks)
实际上,MySQL官网中还提到了一种预测锁,这种锁主要用于存储了空间数据的空间索引,本文暂不讨论。
1、行锁
这MySQL的官方文档中有以下描述:
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.
这句话说明行锁一定是作用在索引上的。
2、间隙锁
在MySQL的官方文档中有以下描述:
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。
这句话表明间隙锁一定是开区间,比如(3,5)或者。在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官网上关于间隙锁还有一段重要描述:
Gap locking can be disabled explicitly. This occurs if you change the transaction isolation level to READ COMMITTED. Under these circumstances, gap locking is disabled for searches and index scans and is used only for foreign-key constraint checking and duplicate-key checking.
3、临键锁
在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的官方文档中还有以下重要描述:
By default, InnoDB operates in REPEATABLE READ transaction isolation level. In this case, InnoDB uses next-key locks for searches and index scans, which prevents phantom rows.
4、共享锁/排他锁
在MySQL的官方文档中有以下描述:
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.
5、意向共享锁/意向排他锁
在MySQL的官方文档中有以下描述:
Intention locks are table-level locks that indicate which type of lock (shared or exclusive) a transaction requires later for a row in a table。
The intention locking protocol is as follows:
Before a transaction can acquire a shared lock on a row in a table, it must first acquire an IS lock or stronger on the table.
Before a transaction can acquire an exclusive lock on a row in a table, it must first acquire an IX lock on the table.
这段话说明意向共享锁/意向排他锁属于表锁,且取得意向共享锁/意向排他锁是取得共享锁/排他锁的前置条件。
共享锁/排他锁与意向共享锁/意向排他锁的兼容性关系:
这里需要重点关注的是IX锁和IX锁是相互兼容的,这是导致上面场景一发生死锁的前置条件,后面会对死锁原因进行详细分析。
6、插入意向锁(IIX)
在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.
这段话表明尽管插入意向锁是一种特殊的间隙锁,但不同于间隙锁的是,该锁只用于并发插入操作。如果说间隙锁锁住的是一个区间,那么插入意向锁锁住的就是一个点。因而从这个角度来说,插入意向锁确实是一种特殊的间隙锁。与间隙锁的另一个非常重要的差别是:尽管插入意向锁也属于间隙锁,但两个事务却不能在同一时间内一个拥有间隙锁,另一个拥有该间隙区间内的插入意向锁(当然,插入意向锁如果不在间隙锁区间内则是可以的)。这里我们再回顾一下共享锁和排他锁:共享锁用于读取操作,而排他锁是用于更新或删除操作。也就是说插入意向锁、共享锁和排他锁涵盖了常用的增删改查四个动作。
7、示例分析
到此为止,我们介绍了MySQL常用的七种锁的前六种,理解了这六种锁之后,才能很好地分析和理解开头给出的两个场景。我们先来分析场景一:
在场景一中,因为IX锁是表锁且IX锁之间是兼容的,因而事务一和事务二都能同时获取到IX锁和间隙锁。另外,需要说明的是,因为我们的隔离级别是RR,且在请求X锁的时候,查询的对应记录都不存在,因而返回的都是间隙锁。接着事务一请求插入意向锁,这时发现事务二已经获取了一个区间间隙锁,而且事务一请求的插入点在事务二的间隙锁区间内,因而只能等待事务二释放间隙锁。这个时候事务二也请求插入意向锁,该插入点同样位于事务一已经获取的间隙锁的区间内,因而也不能获取成功,不过这个时候,MySQL已经检查到了死锁,于是事务二被回滚,事务一提交成功。
分析并理解了场景一,那场景二理解起来就会简单多了:
场景二中,两个间隙锁没有交集,而各自获取的插入意向锁也不是同一个点,因而都能执行成功。
往期精选