前言
我们都知道 MySQL 本身支持表锁,就是锁住整张表,用来阻止其他事务修改或者读取表中的数据:
# 用读锁(共享锁 S)锁住表,阻止其他事务修改表数据
LOCK TABLE `table_name` READ;
# 用写锁(排他锁 X)锁住表,阻止其他事务修改或者读取表数据
LOCK TABLE `table_name` WRITE;复制
同时,MySQL 的 innoDB 引擎又支持行级锁:
# 给特定行添加写锁(排他锁)
SELECT * FROM `table_name` WHERE id = 1 FOR UPDATE;
# 给特定行添加读锁(共享锁)
SELECT * FROM `table_name` WHERE id = 1 LOCK IN SHARE MODE;复制
那么这里就是存在一个问题:当一个事务想要对表添加写锁的时候,理论上必须得先确定这张表上面的记录有没有被其他事务加锁,如果有的话,当前事务应该加锁失败,否则如果当前事务获取了表锁,那它应该可以更新表中的任意行,这显然与另外一个事务拥有的行锁相互冲突。
因此,表锁与行锁应该是有互斥关系的,大致如下:
表排他锁 | 表共享锁 | |
行(页)排他锁 | 互斥 | 互斥 |
行(页)共享锁 | 互斥 | 兼容 |
我们以下面的 user
表为例:
CREATE TABLE `user` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`user_name` varchar(20) DEFAULT NULL COMMENT '用户名',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
INSERT INTO `user` VALUES(1,"aaa"),(2,"bbb"),(3,"ccc");复制
假设现在有两个事务 A 和 B,它们的执行时间线如下表:
事务 A | 事务 B |
BEGIN; | |
SELECT * FROM user WHERE id = 1 FOR UPDATE; | |
LOCK TABLES user READ; (blocked) | |
COMMIT; | |
(success) |
我们会发现,事务 B 获取表锁的操作会被阻塞。而一旦事务 A 提交了,事务 B 就可以成果获取到锁,阻塞是因为事务 B “发现了”当前表里面有一个行排他锁。
这就引申出了一个问题:一个事务在加表锁的时候是如何确定这张表的记录行上面有没有加锁呢?
很显然,最简单的方式就是一行一行遍历每条数据,分别去判断每行有没有被锁住。
很显然,这种方法也是效率特别低下的一个方法,因为它的时间复杂度是 O(n)
,其中 n 是行数,这意味着当数据量越大的时候,对行所的检查效率就越低下,并且是线性增长的。
那么有没有什么办法解决这个问题呢?
很显然,MySQL 提供了更加优秀的选择 -- 意向锁。
什么是意向锁
意向锁是表级锁的一种,它是由数据库引擎自行维护的,用户自己无需也无法操作意向锁。
如果用户想要在表上面添加一个共享锁或者排他锁的时候,需要做如下两个检查:
•检查这张表的排他锁有没有被其他事务占用,如果有,那么加锁失败;•检查这张表中的行锁有没有被其他事务占用,如果有,那么加锁失败。
上文我们讲到,针对第二个检查,如果一张表的数据量特别大,然后我们又想在这张表上添加一个表锁,如果一行一行地去遍历这张表的数据有没有被锁住,效率比较低下。意向锁的存在正是为了解决这个问题。
意向锁能够将检查行锁的时间复杂度由 O(n) 变成 O(1),其加锁的具体做法就是,当一个事务想要获取表中某一行的(共享/排他)锁的时候,它会自动尝试给当前表的加上意向(共享/排他)锁
。然后,表锁和行锁之间的兼容互斥性就变成了表锁和意向锁之间的竞争关系,这就是上面示例中事务 B “发现” 行锁的方式。
意向锁的兼容性和互斥性
有了意向锁的存在,文章开头提到的表锁与行锁之间的竞争关系就变成了表锁与意向锁之间的兼容互斥关系了。需要注意的是,意向锁只会与表级的共享 互斥锁具有互斥性。具体的兼容互斥性可以参考下述表格:
排他锁(X) | 意向排他锁(IX) | 共享锁(S) | 意向共享锁(IS) | |
排他锁(X) | 互斥 | 互斥 | 互斥 | 互斥 |
意向排他锁(IX) | 互斥 | 兼容 | 互斥 | 兼容 |
共享锁(S) | 互斥 | 互斥 | 兼容 | 兼容 |
意向共享锁(IS) | 互斥 | 兼容 | 兼容 | 兼容 |
上表中的排他锁和共享锁针对的都是表级别的,从这里我们可以看出:
•意向锁之间互相兼容,因此针对表中的记录加锁不会因为意向锁而产生互斥,行锁之间的竞争关系是行锁与行锁的竞争,意向锁并不会参与其中;•意向锁和表级锁会产生互斥,这里互斥的作用就是用于表锁的第二个检查。
总结
在使用 MySQL 的过程中,意向锁往往是经常会被我们忽略的一种锁,因为它的存在感确实很低(我们不需要自己去操作它),低到很多同学都不知道它的存在,但它却像一位无名英雄一样,对提高 MySQL 性能和并发安全性提供了重要保障。意向锁表级锁的一种,它只会和表锁具有互斥性,不会影响行锁的加锁。事务获取行锁的时候会先尝试获取意向锁(与表锁进行竞争),如果成功,那么会再去竞争相应的行锁。
respect!
参考
•Mysql 官网:InnoDB Locking[1]