表级锁也有 S 和 X 锁,同时为了支持多种粒度的锁并存,又有意向锁(intention locks)。意向锁是表级锁,作用是表明有事务(一个或多个)有意向锁定表中的行(可能已经加了锁了,也可能正要去加锁)。意向锁在表级锁和行级锁之间起到一个协调的作用,他告诉那些想要给整张表加锁的事务:现在有其他事务正在给表中的行加行锁,你可能要等等哦(具体等不等要看锁之间的兼容性)。
> SELECT * FROM `locks` WHERE `unique` BETWEEN 3 AND 12 FOR UPDATE;
+--------+-------+----------+
| unique | index | no_index |
+--------+-------+----------+
| 5 | 30 | c |
| 6 | 20 | g |
+--------+-------+----------+
2 rows in set (0.048 sec)
> SELECT * FROM `locks` WHERE `index` = 24 FOR UPDATE;
+--------+-------+----------+
| unique | index | no_index |
+--------+-------+----------+
| 25 | 24 | x |
+--------+-------+----------+
1 row in set (0.040 sec)
> SELECT * FROM `locks` WHERE `no_index` = 'a' FOR UPDATE;
+--------+-------+----------+
| unique | index | no_index |
+--------+-------+----------+
| 1 | 30 | a |
+--------+-------+----------+
1 row in set (0.038 sec)
产生的锁为:
> SELECT ENGINE_TRANSACTION_ID, OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, LOCK_TYPE, LOCK_MODE, LOCK_STATUS, LOCK_DATA FROM performance_schema.data_locks;
+-----------------------+---------------+-------------+------------+-----------+-----------+-------------+------------------------+
| ENGINE_TRANSACTION_ID | OBJECT_SCHEMA | OBJECT_NAME | INDEX_NAME | LOCK_TYPE | LOCK_MODE | LOCK_STATUS | LOCK_DATA |
+-----------------------+---------------+-------------+------------+-----------+-----------+-------------+------------------------+
| 69501380 | db_1 | locks | NULL | TABLE | IX | GRANTED | NULL |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | supremum pseudo-record |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 1 |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 5 |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 6 |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 15 |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 25 |
| 69501380 | db_1 | locks | PRIMARY | RECORD | X | GRANTED | 26 |
+-----------------------+---------------+-------------+------------+-----------+-----------+-------------+------------------------+
8 rows in set (0.034 sec)
一些准备
为了更好地观察锁,需要做一些提前准备:
了解 InnoDB 的一些机制
了解事务、隔离级别、B+ 树等。
确保 Performance Schema 已启用
执行以下语句查看 performance_schema 变量的值,1 说明 Performance Schema 已启用,0 说明未启用:
如果是未启用的话,可以参考文档将 Performance Schema 打开。打开后,Performance Schema 存储引擎会操作 performance_schema 数据库里的表,给这些表填充数据。
对几个表的表结构有基本了解
确认事务的隔离级别
需要事先建立这样一个认知:REPEATABLE READ 和 READ COMMITTED 两种隔离级别下的锁有不同表现:
因此,如果发现加锁情况不是自己所预期的那样,可以检查一下隔离级别:
设置隔离级别为自己想要的,例如 REPEATABLE READ:
准备一张表
锁
共享锁和互斥锁
共享锁(shared (S) locks)允许持有锁的事务进行读操作。
互斥锁(exclusive (X) locks)允许持有锁的事务进行写操作(更新/删除)。
不同事务对相同对象加锁时,锁的兼容性如下:
以下为了方便,使用
S
表示共享锁,使用X
表示互斥锁。行级锁和表级锁
InnoDB 支持多种粒度的锁:表级锁和行级锁。并且允许在同一张表上同时出现表级锁和行级锁。
行级锁按照锁的排他性可以分为 S 和 X 锁,兼容性同上所示。
表级锁也有 S 和 X 锁,同时为了支持多种粒度的锁并存,又有意向锁(intention locks)。意向锁是表级锁,作用是表明有事务(一个或多个)有意向锁定表中的行(可能已经加了锁了,也可能正要去加锁)。意向锁在表级锁和行级锁之间起到一个协调的作用,他告诉那些想要给整张表加锁的事务:现在有其他事务正在给表中的行加行锁,你可能要等等哦(具体等不等要看锁之间的兼容性)。
意向锁按照排他性可以分为意向共享锁(intention shared lock (IS))和意向排他锁(intention exclusive lock (IX))。
SELECT ... FOR SHARE
语句会给表加 IS 锁SELECT ... FOR UPDATE
语句会给表加 IX 锁个人经验,一般实践中不会主动给整张表加锁,因此下面不会再讨论表级别的 S 和 X 锁。
意向锁之间是相互兼容的,因为一个事务锁定表中的某几行并不妨碍另一个事务锁定表中的另外几行。
观察行级锁和意向锁
在 READ COMMITTED 隔离级别下观察(因为没有 gap lock,减少干扰)。
在一个连接中,执行以下语句:
在以上语句中
COMMIT
之前,在另一个连接中查看锁:locks
表上有一个表级锁,IX 锁行级锁
行级锁按照锁定范围和作用不同,可以分为
记录锁
记录锁锁定索引记录。如果一个表没有用户定义的索引,InnoDB 会自动创建一个隐藏的 clustered index 并在这个索引上加锁。
观察记录锁
(一些操作的细节如上,不再赘述)
在 READ COMMITTED 隔离级别下,一条这样的语句:
加锁情况如下:
LOCK_MODE
都是S,REC_NOT_GAP
,强调这些锁都是记录锁,不涉及间隙锁index
的 3 条索引记录:30, 1
,30, 5
,30, 26
1
,5
,26
间隙锁
间隙锁在索引记录之间的间隙上加锁。
间隙锁的唯一作用是阻止其他事务向间隙中插入,避免幻行(phantom rows)的发生。间隙锁是相互兼容的,共享间隙锁和排他间隙锁在功能上完全相同,不同事务可以在同一个间隙上持有相互冲突的锁。
READ COMMITTED 隔离级别下,间隙锁被禁用,只用于外键约束检查和 duplicate-key 检查。这是一种在性能和并发之间的权衡。
观察间隙锁
同样的语句:
在不同隔离级别下加锁不同。
REPEATABLE READ 隔离级别下的间隙锁
6
和15
之间的间隙(6, 15)
中插入,一个间隙锁(S,GAP
)锁住了这个间隙READ COMMITTED 隔离级别下的间隙锁
next-key lock
next-key lock 是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。
观察 next-key lock
同样的语句:
在不同隔离级别下加锁不同。
REPEATABLE READ 隔离级别下
X
后面为空),1 个间隙锁(X,GAP
)5
和6
上,锁住的区间为(1, 5]
和(5, 6]
(6, 15)
,这里没有必要锁住15
READ COMMITTED 隔离级别下
5
和6
两个主键索引记录被加了记录锁,不涉及间隙锁performance_schema.data_locks
表的LOCK_MODE
字段辨别记录锁、间隙锁、next-key lock:
LOCK_MODE
在 REPEATABLE READ 隔离级别下做进一步观察
对非唯一索引列加锁
语句:
产生的锁为:
index
被锁住的区间为(20, 24]
和(24, 30)
25
上也被加了记录锁对无索引列加锁
语句:
产生的锁为:
supremum pseudo-record
是一个伪记录,大于表里所有已有的主键索引记录插入意向锁(Insert Intention Locks)
插入意向锁是一种间隙锁,作用是:多个事务向同一个索引间隙中插入时,如果插入的位置不同,则不必相互等待。
// TODO: 插入意向锁能观察到吗?
其他锁
AUTO-INC Locks 是一种特殊的表级锁,和
AUTO_INCREMENT
有关。参见
进一步阅读