锁(lock)是计算机用以协调多个进程间并发(concurrent)访问同一共享资源的一种机制。MySQL 中为了保证数据访问的一致性与有效性等功能,实现了锁机制。MySQL 中的锁是在服务器层或者存储引擎层实现的。
锁的功能
我们需要在最大程度地利用数据库的并发访问能力的同时,也要确保每个用户能以一致的方式读取和修改数据。但是,事务并发执行时可能会带来各种问题(详见数据库事务)。要解决并发事务的问题,就需要借助到锁。
锁的分类
flowchart TB 模式分类 --> 乐观锁 模式分类 --> 悲观锁 粒度分类 --> 全局锁 粒度分类 --> 表级锁 粒度分类 --> 页级锁 粒度分类 --> 行级锁 属性分类 --> id1[共享锁(S)] 属性分类 --> id2[排他锁(X)] 状态分类 --> 意向共享锁 状态分类 --> 意向排他锁 算法分类 --> 间隙锁 算法分类 --> 临键锁 算法分类 --> 记录锁
乐观/悲观锁
乐观锁
乐观锁认为数据一般情况下不会造成冲突,所以在数据提交更新的时候才会去检测。适用于读多写少的场景。
实现
乐观锁是基本版本号机制实现的。表中增加一个 version
字段,读取数据时将 version
一起读出。数据每更新一次,version += 1
。当修改需要提交时,将读取时的
version 与表当前的 version
做比较:如果一致,说明在此期间无人修改这条记录,不一致则说明已经被修改了,提交失败。
悲观锁
悲观锁认为数据每次操作都会被修改,所以在每次操作数据时都会加上锁。适用于并发量不大、写多读少的场景。
实现
通过共享锁和排他锁实现。
共享/排他锁
共享锁
共享锁,又称读锁,简称 S 锁。当事务对某数据加上读锁后,其他事务只能对该数据加读锁,不能加写锁。
实现
1 |
|
排他锁
排他锁,又称为写锁,简称 X 锁,当事务对数据加上排他锁后,其他事务无法对该数据进行查询或者修改。
MySQL InnoDB 引擎默认
update
,delete
,insert
都会自动给涉及到的数据加上排他锁,select
语句默认不会加任何锁类型。
实现
1 |
|
兼容性
共享锁(S) | 排他锁(X) | |
---|---|---|
共享锁(S) | 兼容 | 互斥 |
排他锁(X) | 互斥 | 互斥 |
粒度锁
全局锁
对整个 MySQL 数据库实例加锁,加锁期间,对数据库的任何增删改操作都无法执行。只适用于全库数据备份。
实现
1 |
|
表级锁
给当前操作的这张表加锁。
实现
1 |
|
页级锁
表级锁速度快,但冲突多,行级冲突少,但速度慢。因此采取了折衷的页级锁,一次锁定相邻的一组记录。BDB 引擎支持页级锁。
行级锁
发生锁冲突概率最低,但是加锁慢,开销大。MySQL 中只有 InnoDB 引擎支持行锁,其他不支持。
实现
MySQL 中,行级锁并不是之间锁记录,而是锁的索引。MySQL 在执行
update
、delete
语句时会自动加上行锁。
意向锁
意向锁是表锁。为了协调行锁和表锁的关系,支持多粒度(表锁与行锁)的锁并存。
作用
当有事务 A 有行锁时,MySQL会自动为该表添加意向锁。事务 B 如果想申请整个表的写锁,那么不需要遍历每一行判断是否存在行锁,而直接判断是否存在意向锁,增强性能。
实现
意向锁是由数据引擎自己维护的,用户无法手动操作意向锁。在为数据行加共享/排他锁之前,InnoDB 会先获取该数据行所在在数据表的对应意向锁。
兼容性
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
意向共享锁(IS) | 兼容 | 兼容 |
意向排他锁(IX) | 兼容 | 兼容 |
意向共享锁(IS) | 意向排他锁(IX) | |
---|---|---|
共享锁 | 兼容 | 互斥 |
排他锁 | 互斥 | 互斥 |
间隙/临键/记录锁
记录锁、间隙锁、临键锁都是排它锁。
记录锁(Record Lock)
记录锁锁定一条记录,记录锁也叫行锁。
1 |
|
间隙锁(Gap Lock)
间隙锁锁定一段区间内的索引记录,而不仅仅是这个区间中的每一条数据。
1 |
|
即所有在 [1,10)
区间内的记录行都会被锁住,所有id 为
1、2、3、4、5、6、7、8、9 的数据行的插入会被阻塞
临键锁(Next-Key Lock)
临键锁是记录锁与间隙锁的组合。其封锁范围既包含索引记录,又包含左闭右开的索引区间。临键锁主要为了避免幻读。
每个数据行上的非唯一索引列上都会存在一把临键锁,当某个事务持有该数据行的临键锁时,会锁住一段左开右闭区间的数据。需要强调的一点是,InnoDB 中行级锁是基于索引实现的,临键锁只与非唯一索引列有关,在唯一索引列(包括主键列)上不存在临键锁。