We read every piece of feedback, and take your input very seriously.
To see all available qualifiers, see our documentation.
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
MySQL 具有非常多的锁,从不同的维度可以将它们分成很多类,这篇文档会从不同类别出发分析 MySQL 的各种锁。
对整个数据库实例进行加锁,全局加锁之后会阻塞数据的增删改操作以及建表和修改表结构的操作,开发人员一般用的很少。
对整张表上锁,InnoDB 引擎在不通过索引进行数据加锁时,会将整张表锁住。
表锁加锁快、开销小、不会死锁,但是很容易冲突。
除了 InnoDB 自动升级锁之外,还可以通过 LOCK TABLES xxx READ/WRITE; 进行表加锁操作。
对应的可以通过 UNLOCK 语法释放表锁,但是需要注意以下几点:
页面锁是表锁和记录锁的折中方案,基于索引页面进行锁定。BDB 引擎支持这种粒度的锁,InnoDB 并不支持。
也称为行锁,这里称为记录锁是因为 MySQL 官方文档的名称是 "Record Locks"。
InnoDB 基于索引记录对数据行进行加锁,如果 SQL 语句不是基于索引进行的操作,则不会加记录锁,取而代之的是加表锁。
记录锁开销大,数量多,容易死锁,但是颗粒度、并发度最高。
下面是几种存储引擎对于三种锁的支持情况:
乐观锁是指以最乐观的情况来估计可能面对的场景,乐观的情况就是大部分场景都不需要加锁,一般是读多于写的时候。
乐观锁需要自己编写代码进行支持,在更新数据之前手动判断原来的记录是否已经被更改,如果没有更改则更新成功,否则更新失败。
一般会在表记录中添加一个版本号字段进行标记,每次更新记录的时候版本号都会加一,乐观锁进行更新的时候会先判断版本号是否发生了变化。
悲观锁假定每次进行更新的时候都会有冲突,需要加上排他锁确保自己在使用数据或者更新数据的时候不会受到其他 session 的影响。
共享锁和排他锁(第三节)都属于悲观锁,它们是悲观锁的不同实现。
悲观锁适用于并发量不大,更新写入比较多,数据一致性要求高的场景。
共享锁(Shared Locks,也称为 S 锁),可以理解为是读锁,S 锁可以被多个 session 同时持有,拥有 S 锁的 session 都可以对加锁的数据行进行读取,主要用于防止读取的数据被更改。
语法:
select ... lock in share mode;
排他锁(eXclusive Locks,也称为 X 锁),可以理解为是写锁,X 锁同一时刻只能被一个 session 持有,持有 X 锁的的 session 可以对加锁的数据行进行读取和修改操作,主要是为了保证数据更改的一致性,避免脏读和不可重复读的问题。
X 锁和其他锁(包括 X 锁和 S 锁)互斥。
select ... for update;
意向锁(Intention Locks)是 InnoDB 用来支持多粒度锁(表锁和记录锁)同时存在的一种机制,意向锁是表级锁,主要是用来标记当前表中是否有数据行被加锁了。
意向锁分为意向共享锁(Intention Shared Locks, IS)和意向排他锁(Intention eXclusive Locks, IX)。
当数据表中的某一行记录被加上了 X 锁时,该表会被对应的加上 IX 锁;当数据表中的某一行记录被加上了 S 锁时,该表会对应加上 IS 锁。
当表中的某一行数据加上了记录锁时,InnoDB 会自动给对应的表加上意向锁,这一操作不需要用户进行干涉。
数据表拥有意向锁之后,下一次其他 session 想要给数据表加上表级锁(X 锁或者 S 锁)就可以直接通过意向锁进行判断表中是否有数据行已经持有锁了,这样就不需要在针对表加锁之前遍历每一行数据进行加锁情况的判断了。
意向锁以及表级锁的兼容情况:
在索引记录上加锁,结果是会锁定一条数据。针对 InnoDB,如果索引失效记录锁会升级为表锁。
间隙锁(Gap Locks),当我们使用范围条件而不是相等条件检索数据并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”。
InnoDB 也会对这个间隙加锁,这种锁机制就叫做间隙锁。间隙锁的主要目的是为了避免幻读现象,放置在当前 session 读取一系列数据之后,其他 session 在“间隙”处新增数据或者删除已有的数据,以至于下一次当前 session 再次读取数据时出现前后数据总数不一致的幻读现象。
间隙锁锁定的是区间内的所有数据以及间隙,不仅仅是已经存在的数据。
需要注意的是间隙锁加锁时不会包括条件语句中的“边界”数据,比如:
select * from table where id between 1 and 10 for update;
这里的间隙锁范围为 (1,10),也就是说 id 为 1 和 10 的数据行不会被加锁。
间隙锁的加锁过程有很多种情况,比较复杂:
唯一索引的间隙锁:
id
普通索引的间隙锁:
这一块非常复杂,比较难理解,在对数据进行加锁的时候需要十分谨慎,间隙锁可能会给意料之外的间隙加上锁从而导致死锁。另外一点是如果事务隔离级别是读已提交(RC)的话,间隙锁会失效。
具体参考:MySQL 的锁机制 - 记录锁、间隙锁、临键锁。
临键锁(Next-Key Locks)是间隙锁和记录锁的结合,锁定范围既包括记录又包括记录间隙。
临键锁的主要目的也是为了避免幻读,并且在事务隔离级别降为 RC 时失效。
根据 《MySQL 实战 45 讲》,加锁规则可以总结为“两个原则”、“两个优化”和“一个 bug”:
牢记以上加锁规则,可以较为轻松的分析加锁范围。
The text was updated successfully, but these errors were encountered:
No branches or pull requests
MySQL 具有非常多的锁,从不同的维度可以将它们分成很多类,这篇文档会从不同类别出发分析 MySQL 的各种锁。
1 粒度分类
1.1 全局锁
对整个数据库实例进行加锁,全局加锁之后会阻塞数据的增删改操作以及建表和修改表结构的操作,开发人员一般用的很少。
1.2 表锁
对整张表上锁,InnoDB 引擎在不通过索引进行数据加锁时,会将整张表锁住。
表锁加锁快、开销小、不会死锁,但是很容易冲突。
除了 InnoDB 自动升级锁之外,还可以通过 LOCK TABLES xxx READ/WRITE; 进行表加锁操作。
对应的可以通过 UNLOCK 语法释放表锁,但是需要注意以下几点:
1.3 页面锁
页面锁是表锁和记录锁的折中方案,基于索引页面进行锁定。BDB 引擎支持这种粒度的锁,InnoDB 并不支持。
1.4 记录锁
也称为行锁,这里称为记录锁是因为 MySQL 官方文档的名称是 "Record Locks"。
InnoDB 基于索引记录对数据行进行加锁,如果 SQL 语句不是基于索引进行的操作,则不会加记录锁,取而代之的是加表锁。
记录锁开销大,数量多,容易死锁,但是颗粒度、并发度最高。
下面是几种存储引擎对于三种锁的支持情况:
2 模式分类
2.1 乐观锁
乐观锁是指以最乐观的情况来估计可能面对的场景,乐观的情况就是大部分场景都不需要加锁,一般是读多于写的时候。
乐观锁需要自己编写代码进行支持,在更新数据之前手动判断原来的记录是否已经被更改,如果没有更改则更新成功,否则更新失败。
一般会在表记录中添加一个版本号字段进行标记,每次更新记录的时候版本号都会加一,乐观锁进行更新的时候会先判断版本号是否发生了变化。
2.2 悲观锁
悲观锁假定每次进行更新的时候都会有冲突,需要加上排他锁确保自己在使用数据或者更新数据的时候不会受到其他 session 的影响。
共享锁和排他锁(第三节)都属于悲观锁,它们是悲观锁的不同实现。
悲观锁适用于并发量不大,更新写入比较多,数据一致性要求高的场景。
3 属性分类
3.1 共享锁
共享锁(Shared Locks,也称为 S 锁),可以理解为是读锁,S 锁可以被多个 session 同时持有,拥有 S 锁的 session 都可以对加锁的数据行进行读取,主要用于防止读取的数据被更改。
语法:
3.2 排他锁
排他锁(eXclusive Locks,也称为 X 锁),可以理解为是写锁,X 锁同一时刻只能被一个 session 持有,持有 X 锁的的 session 可以对加锁的数据行进行读取和修改操作,主要是为了保证数据更改的一致性,避免脏读和不可重复读的问题。
X 锁和其他锁(包括 X 锁和 S 锁)互斥。
语法:
4 状态分类
4.1 意向锁
意向锁(Intention Locks)是 InnoDB 用来支持多粒度锁(表锁和记录锁)同时存在的一种机制,意向锁是表级锁,主要是用来标记当前表中是否有数据行被加锁了。
意向锁分为意向共享锁(Intention Shared Locks, IS)和意向排他锁(Intention eXclusive Locks, IX)。
当数据表中的某一行记录被加上了 X 锁时,该表会被对应的加上 IX 锁;当数据表中的某一行记录被加上了 S 锁时,该表会对应加上 IS 锁。
当表中的某一行数据加上了记录锁时,InnoDB 会自动给对应的表加上意向锁,这一操作不需要用户进行干涉。
数据表拥有意向锁之后,下一次其他 session 想要给数据表加上表级锁(X 锁或者 S 锁)就可以直接通过意向锁进行判断表中是否有数据行已经持有锁了,这样就不需要在针对表加锁之前遍历每一行数据进行加锁情况的判断了。
意向锁以及表级锁的兼容情况:
5 算法分类
5.1 记录锁
在索引记录上加锁,结果是会锁定一条数据。针对 InnoDB,如果索引失效记录锁会升级为表锁。
5.2 间隙锁
间隙锁(Gap Locks),当我们使用范围条件而不是相等条件检索数据并请求共享或排他锁时,InnoDB 会给符合条件的已有数据记录的索引项加锁。对于键值在条件范围内但并不存在的记录,叫做“间隙”。
InnoDB 也会对这个间隙加锁,这种锁机制就叫做间隙锁。间隙锁的主要目的是为了避免幻读现象,放置在当前 session 读取一系列数据之后,其他 session 在“间隙”处新增数据或者删除已有的数据,以至于下一次当前 session 再次读取数据时出现前后数据总数不一致的幻读现象。
间隙锁锁定的是区间内的所有数据以及间隙,不仅仅是已经存在的数据。
需要注意的是间隙锁加锁时不会包括条件语句中的“边界”数据,比如:
这里的间隙锁范围为 (1,10),也就是说 id 为 1 和 10 的数据行不会被加锁。
间隙锁的加锁过程有很多种情况,比较复杂:
唯一索引的间隙锁:
id
= 5 FOR UPDATE;id
BETWEEN 5 AND 7 FOR UPDATE;普通索引的间隙锁:
这一块非常复杂,比较难理解,在对数据进行加锁的时候需要十分谨慎,间隙锁可能会给意料之外的间隙加上锁从而导致死锁。另外一点是如果事务隔离级别是读已提交(RC)的话,间隙锁会失效。
具体参考:MySQL 的锁机制 - 记录锁、间隙锁、临键锁。
5.3 临键锁
临键锁(Next-Key Locks)是间隙锁和记录锁的结合,锁定范围既包括记录又包括记录间隙。
临键锁的主要目的也是为了避免幻读,并且在事务隔离级别降为 RC 时失效。
6 其他细节
7 加锁规则
根据 《MySQL 实战 45 讲》,加锁规则可以总结为“两个原则”、“两个优化”和“一个 bug”:
牢记以上加锁规则,可以较为轻松的分析加锁范围。
8 参考
The text was updated successfully, but these errors were encountered: