MySQL锁

锁(lock)是计算机用以协调多个进程间并发(concurrent)访问同一共享资源的一种机制。MySQL 中为了保证数据访问的一致性与有效性等功能,实现了锁机制。MySQL 中的锁是在服务器层或者存储引擎层实现的。

锁的功能

我们需要在最大程度地利用数据库的并发访问能力的同时,也要确保每个用户能以一致的方式读取和修改数据。但是,事务并发执行时可能会带来各种问题(详见数据库事务)。要解决并发事务的问题,就需要借助到锁。

锁的分类

flowchart TB
  模式分类 --> 乐观锁
  模式分类 --> 悲观锁

  粒度分类 --> 全局锁
  粒度分类 --> 表级锁
  粒度分类 --> 页级锁
  粒度分类 --> 行级锁

  属性分类 --> id1[共享锁(S)]
  属性分类 --> id2[排他锁(X)]

  状态分类 --> 意向共享锁
  状态分类 --> 意向排他锁

  算法分类 --> 间隙锁
  算法分类 --> 临键锁
  算法分类 --> 记录锁

乐观/悲观锁

乐观锁

乐观锁认为数据一般情况下不会造成冲突,所以在数据提交更新的时候才会去检测。适用于读多写少的场景。

实现

乐观锁是基本版本号机制实现的。表中增加一个 version 字段,读取数据时将 version 一起读出。数据每更新一次,version += 1。当修改需要提交时,将读取时的 version 与表当前的 version 做比较:如果一致,说明在此期间无人修改这条记录,不一致则说明已经被修改了,提交失败。

悲观锁

悲观锁认为数据每次操作都会被修改,所以在每次操作数据时都会加上锁。适用于并发量不大、写多读少的场景。

实现

通过共享锁和排他锁实现。

共享/排他锁

共享锁

共享锁,又称读锁,简称 S 锁。当事务对某数据加上读锁后,其他事务只能对该数据加读锁,不能加写锁。

实现

1
2
3
4
# MySQL 5.7 和 MySQL 8.0
SELECT ... LOCK IN SHARE; MODE
# MySQL 8.0
SELECT ... FOR SHARE;

排他锁

排他锁,又称为写锁,简称 X 锁,当事务对数据加上排他锁后,其他事务无法对该数据进行查询或者修改。

MySQL InnoDB 引擎默认 updatedeleteinsert 都会自动给涉及到的数据加上排他锁,select 语句默认不会加任何锁类型。

实现

1
SELECT ... FOR UPDATE;

兼容性

共享锁(S) 排他锁(X)
共享锁(S) 兼容 互斥
排他锁(X) 互斥 互斥

粒度锁

全局锁

对整个 MySQL 数据库实例加锁,加锁期间,对数据库的任何增删改操作都无法执行。只适用于全库数据备份。

实现

1
Flush tables with read lock (FTWRL)

表级锁

给当前操作的这张表加锁。

实现

1
2
LOCK TABLE READ
LOCK TABLE WRITE

页级锁

表级锁速度快,但冲突多,行级冲突少,但速度慢。因此采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 引擎支持页级锁。

行级锁

发生锁冲突概率最低,但是加锁慢,开销大。MySQL 中只有 InnoDB 引擎支持行锁,其他不支持。

实现

MySQL 中,行级锁并不是之间锁记录,而是锁的索引。MySQL 在执行 updatedelete 语句时会自动加上行锁。

意向锁

意向锁是表锁。为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。

作用

当有事务 A 有行锁时,MySQL会自动为该表添加意向锁。事务 B 如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。

实现

意向锁是由数据引擎自己维护的,用户无法手动操作意向锁。在为数据行加共享/排他锁之前,InnoDB 会先获取该数据行所在在数据表的对应意向锁。

兼容性

意向共享锁(IS) 意向排他锁(IX)
意向共享锁(IS) 兼容 兼容
意向排他锁(IX) 兼容 兼容
意向共享锁(IS) 意向排他锁(IX)
共享锁 兼容 互斥
排他锁 互斥 互斥

间隙/临键/记录锁

记录锁、间隙锁、临键锁都是排它锁。

记录锁(Record Lock)

记录锁锁定一条记录,记录锁也叫行锁。

1
select * from user where id = 1 for update;

间隙锁(Gap Lock)

间隙锁锁定一段区间内的索引记录,而不仅仅是这个区间中的每一条数据。

1
select * from user where id < 10 for update;

即所有在 [1,10) 区间内的记录行都会被锁住,所有id 为 1、2、3、4、5、6、7、8、9 的数据行的插入会被阻塞

临键锁(Next-Key Lock)

临键锁是记录锁与间隙锁的组合。其封锁范围既包含索引记录,又包含左闭右开的索引区间。临键锁主要为了避免幻读。

每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。

参考资料