MySQL死锁及源码分析!

本文涉及的产品
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,高可用系列 2核4GB
简介: MySQL死锁及源码分析!

表锁

表锁由MySQL服务器实现,无关存储引擎。

在执行 DDL语句时,会对整张表加锁。

可以使用 LOCK TABLES TABLE_NAME READ/WRITE 显示对某个表加锁。同时,需要锁的会话必须在单个语句中获取它所需的所有锁。

表锁加锁规则:

  • 对于读锁
  • 持有读锁的会话可以读表,但不能写表;
  • 允许多个会话同时持有读锁;
  • 其他会话就算没有给表加读锁,也是可以读表的,但是不能写表;
  • 其他会话申请该表写锁时会阻塞,直到锁释放。
  • 对于写锁
  • 持有写锁的会话既可以读表,也可以写表;
  • 只有持有写锁的会话才可以访问该表,其他会话访问该表会被阻塞,直到锁释放;
  • 其他会话无论申请该表的读锁或写锁,都会阻塞,直到锁释放

表锁释放规则:

  • 使用 UNLOCK TABLES 语句可以显示释放表锁;
  • 如果会话在持有表锁的情况下执行 LOCK TABLES 语句,将会释放该会话之前持有的锁;
  • 如果会话在持有表锁的情况下执行 START TRANSACTIONBEGIN 开启一个事务,将会释放该会话之前持有的锁;
  • 如果会话连接断开,将会释放该会话所有的锁

行锁

共享锁和排他锁
  • 共享锁 S,允许持有该锁的事务读取一行
  • 排他锁 X,允许持有该锁的事务更新或删除行
意向锁

InnoDB支持 多粒度锁定,允许行锁和表锁共存,但是表锁和行锁之间是有冲突的。为了使多粒度级别的锁定切实可行,InnoDB使用意图锁来表示事务在某一行的操作,如此就不用遍历表中的记录来看是否加了行锁。意向锁是表级锁。

  • 意向共享锁 IS,表示事务打算在表中的某一行上设置S共享锁
  • 意向排他锁 IX,表示事务打算在表中的某一行上设置X排他锁
X IX S IS
X 冲突 冲突 冲突 冲突
IX 冲突 兼容的 冲突 兼容的
S 冲突 冲突 兼容的 兼容的
IS 冲突 兼容的 兼容的 兼容的
TABLE LOCK table `test`.`t` trx id 10080 lock mode IX
记录锁

记录锁是索引记录上的锁。记录锁始终锁定索引记录,即使表定义为没有索引。

对于这种情况, InnoDB创建一个隐藏的聚集索引并使用该索引进行记录锁定。

如果 WHERE 条件中指定的列是个二级索引,那么记录锁不仅会加在这个二级索引上,还会加在这个二级索引所对应的聚簇索引上。

文章内容收录到个人网站,方便阅读hardyfish.top/

文章内容收录到个人网站,方便阅读hardyfish.top/

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10078 lock_mode X locks rec but not gap
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;
间隙锁

间隙锁是对索引记录之间间隙的锁定,或者加在第一个索引之前,或最后一个索引之后的间隙。这个范围可以跨一个索引记录,多个索引记录,甚至是空的。

对于使用唯一索引锁定行来搜索唯一行的语句,不需要间隙锁定。

允许存在冲突的间隙锁的原因是,如果从索引中清除记录,则必须合并由不同事务持有的记录上的间隙锁。

间隙锁可以共存。一个事务获取的间隙锁不会阻止另一事务在同一间隙上获取间隙锁。共享间隙锁和独占间隙锁之间没有区别。它们彼此不冲突,并且执行相同的功能。

间隙锁定可以显式禁用。如果您将事务隔离级别更改为 READ COMMITTED或启用 innodb_locks_unsafe_for_binlog系统变量(现已弃用)

使用 READCOMMITTED 隔离级别。对于 UPDATE 语句,InnoDB 执行“半一致”读操作,以便将最新提交的版本返回给 MySQL,这样 MySQL 就可以确定该行是否匹配 UPDATE 的 WHERE 条件。

RECORD LOCKS space id 35 page no 4 n bits 72 index uniq_kid_aid_biz_rid of table `test`.`t4` 
trx id 3857 lock_mode X locks gap before rec
Record lock, heap no 3 PHYSICAL RECORD: n_fields 5; compact format; info bits 0
 0: len 4; hex 00000014; asc     ;;
 1: len 4; hex 00000001; asc     ;;
 2: len 4; hex 00000001; asc     ;;
 3: len 6; hex 72657461696c; asc retail;;
 4: len 8; hex 0000000000000002; asc         ;;
临键锁

临键锁是索引记录上的记录锁和索引记录之前的间隙上的间隙锁的组合。临键锁一般是左开右闭的区间,还有上确界。(-∞, 10]、(10, 11]、(11, 13]、(13, 20]、(20, +∞)

RECORD LOCKS space id 58 page no 3 n bits 72 index `PRIMARY` of table `test`.`t`
trx id 10080 lock_mode X
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;
Record lock, heap no 2 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 8000000a; asc     ;;
 1: len 6; hex 00000000274f; asc     'O;;
 2: len 7; hex b60000019d0110; asc        ;;
插入意向锁

插入意向锁是一种间隙锁,是进行insert插入行之前的操作。

插入同一索引间隙的多个事务如果没有插入间隙内的同一位置,则无需彼此等待。 假设存在值为 4 和 7 的索引记录。分别尝试插入值 5 和 6  的单独事务在获得插入行上的排他锁之前,每个事务都使用插入意向锁锁定 4 和 7 之间的间隙,但不要互相阻塞,因为行不冲突。

客户端A创建一个包含两条索引记录(90和102)的表,然后启动一个事务,对ID大于100的索引记录放置临键锁。临键锁包括记录102之前的间隙锁:

mysql> CREATE TABLE child (id int(11) NOT NULL, PRIMARY KEY(id)) ENGINE=InnoDB;
mysql> INSERT INTO child (id) values (90),(102);
mysql> START TRANSACTION;
mysql> SELECT * FROM child WHERE id > 100 FOR UPDATE;
+-----+
| id  |
+-----+
| 102 |
+-----+

客户端 B 开始事务以将记录插入到间隙中。事务在等待获取排它锁时获取插入意向锁。

mysql> START TRANSACTION;
mysql> INSERT INTO child (id) VALUES (101);
RECORD LOCKS space id 31 page no 3 n bits 72 index `PRIMARY` of table `test`.`child`
trx id 8731 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 4; hex 80000066; asc    f;;
 1: len 6; hex 000000002215; asc     " ;;
 2: len 7; hex 9000000172011c; asc     r  ;;...
AUTO-INC 锁

AUTO-INC 锁是插入到包含列的表中的事务所采用的特殊表级锁 AUTO_INCREMENT。在最简单的情况下,如果一个事务正在将值插入表中,则任何其他事务都必须等待才能向该表中执行自己的插入操作,以便第一个事务插入的行接收连续的主键值。

该innodb_autoinc_lock_mode变量控制用于自动增量锁定的算法。它允许您选择如何在可预测的自动增量值序列和插入操作的最大并发度之间进行权衡。

行锁的兼容矩阵
RECORD GAP NEXT-KEY INSERT Intention
RECORD
GAP
NEXT-KEY
INSERT Intention

InnoDB中不同SQL语句设置的锁

dev.mysql.com/doc/refman/…

InnoDB设置特定类型的锁如下:

  • SELECT ... FROM是一致性读,读取数据库的快照并且不设置锁,除非将事务隔离级别设置为 SERIALIZABLE。对于 SERIALIZABLE级别,搜索在遇到的索引记录上设置共享的临键锁。但是,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁。
  • 对于SELECT ... FOR UPDATESELECT ... LOCK IN SHARE MODE,会为扫描的行获取锁,并且预计会为不符合包含在结果集中的行释放锁(例如,如果它们不满足子句中给出的条件WHERE)。但是,在某些情况下,行可能不会立即解锁,因为结果行与其原始源之间的关系在查询执行期间丢失。例如,在一个 UNION,在评估表中扫描(并锁定)的行是否符合结果集之前,可能会将这些行插入到临时表中。在这种情况下,临时表中的行与原始表中的行的关系将丢失,并且原始表中的行直到查询执行结束才解锁。
  • SELECT ... LOCK IN SHARE MODE对搜索遇到的所有索引记录设置共享的临键锁。但是,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁。
  • SELECT ... FOR UPDATE对搜索遇到的每个记录设置独占的临键锁锁定。但是,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁。 对于搜索遇到的索引记录, SELECT ... FOR UPDATE会阻止其他会话在某些事务隔离级别中执行 SELECT ... LOCK IN SHARE MODE或读取操作。一致读取会忽略对读取视图中存在的记录设置的任何锁定。
  • UPDATE ... WHERE ...对搜索遇到的每个记录设置独占的临键锁锁定。但是,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁。
  • UPDATE修改聚集索引记录时,将对受影响的辅助索引记录进行隐式锁定。UPDATE在插入新的二级索引记录之前执行重复检查扫描时,以及插入新的二级索引记录时,该操作还会对受影响的二级索引记录获取共享锁 。
  • DELETE FROM ... WHERE ...对搜索遇到的每个记录设置独占的临键锁锁定。但是,对于使用唯一索引锁定行来搜索唯一行的语句,只需要索引记录锁。
  • INSERT在插入的行上设置排它锁。该锁是索引记录锁,而不是临键锁(即没有间隙锁),并且不会阻止其他会话插入到插入行之前的间隙中。  在插入行之前,会设置一种称为插入意向锁的间隙锁。此锁表明插入的意图是,插入同一索引间隙的多个事务如果没有插入间隙内的同一位置,则无需互相等待。  如果发生重复键错误,则会在重复索引记录上设置共享锁。如果另一个会话已经拥有排它锁,则如果多个会话尝试插入同一行,则使用共享锁可能会导致死锁。
CREATE TABLE t1 (i INT, PRIMARY KEY (i)) ENGINE = InnoDB;
会话1:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
会话2:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
会话3:
START TRANSACTION;
INSERT INTO t1 VALUES(1);
会话1:
ROLLBACK;
  • 会话 1 的第一个操作获取该行的排他锁。会话 2 和 3 的操作都会导致重复键错误,并且它们都请求该行的共享锁。当会话 1  回滚时,它会释放其对该行的独占锁,并且会话 2 和 3 的排队共享锁请求将被授予。此时,会话 2 和会话 3  发生死锁:由于对方持有共享锁,双方都无法获取该行的排他锁。
  • INSERT ... ON DUPLICATE KEY UPDATE与简单的INSERT不同之处在于,当发生重复键错误时,将在要更新的行上放置排他锁而不是共享锁。对重复的主键值采用独占索引记录锁定。对重复的唯一键值采取独占的临键锁锁定。
sql语句 加锁
SELECT ... FROM 不加锁
SELECT ... LOCK IN SHARE MODE 共享的临键锁(搜索到的)/记录锁(唯一索引)
SELECT ... FOR UPDATE 排他的临键锁(搜索到的)/记录锁(唯一索引)
UPDATE ... WHERE ... 排他的临键锁(搜索到的)/记录锁(唯一索引)
DELETE FROM ... WHERE ... 排他的临键锁(搜索到的)/记录锁(唯一索引)
INSERT 排他的记录锁,插入行前的插入意向锁,唯一键冲突时设置共享锁
INSERT ... ON DUPLICATE KEY UPDATE 排他的记录锁,插入行前的插入意向锁,唯一键冲突时设置排他锁

delete 和update 更新的行不存在的时候会加 间隙锁

死锁案例

github.com/aneasystone…

事务一语句 事务二语句 事务一等待锁 事务二等待锁 事务二持有锁 案例
事务一语句 事务二语句 事务一等待锁 事务二等待锁 事务二持有锁 案例
insert insert lock_mode X insert intention lock_mode X insert intention lock_mode X 1
insert insert lock_mode X locks gap before rec insert intention lock_mode X locks gap before rec insert intention lock_mode X locks gap before rec 14
insert insert lock_mode X insert intention lock_mode X insert intention lock_mode S 2
insert insert lock mode S lock_mode X locks gap before rec insert intention lock_mode X locks rec but not gap 15
delete insert lock_mode X locks rec but not gap lock mode S lock_mode X locks rec but not gap 18
delete delete lock_mode X lock mode S lock_mode X locks rec but not gap 4
delete delete lock_mode X lock mode X lock_mode X locks rec but not gap 6
delete delete lock_mode X locks rec but not gap lock_mode X lock_mode X 3
delete delete lock_mode X locks rec but not gap lock mode X lock_mode X locks rec but not gap 7
delete delete lock_mode X locks rec but not gap lock_mode X locks rec but not gap lock_mode X locks rec but not gap 8,9
delete insert lock_mode X lock_mode X locks gap before rec insert intention lock_mode X locks rec but not gap 5
delete insert lock_mode X lock_mode X locks gap before rec insert intention lock_mode S 10
delete insert lock_mode X lock_mode X locks gap before rec insert intention lock_mode X 12
delete insert lock_mode X lock mode S lock_mode X locks rec but not gap 13
update update lock_mode X locks rec but not gap lock mode S lock_mode X locks rec but not gap 11
update update lock_mode X lock_mode X locks gap before rec insert intention lock_mode X locks rec but not gap 16
update update lock_mode X locks gap before rec insert intention lock_mode X locks gap before rec insert intention lock_mode X 17
update delete lock_mode X locks rec but not gap lock_mode X lock mode S 19
update update lock_mode X locks rec but not gap waiting lock_mode X locks rec but not gap waiting lock_mode X locks rec but not gap 20

macOS CLion 编译调试 MySQL 5.7

参考链接:shockerli.net/post/mysql-…

官方文档:https://dev.mysql.com/doc/mysql-sourcebuild-excerpt/5.7/en/source-installation.html

  1. 下载源码 官网下载或者github上下载github.com/mysql/mysql…
  2. 配置CMake
-DWITH_DEBUG=1                     # 开启DEBUG模式
-DDOWNLOAD_BOOST=1                 # boost不存在时自动下载
-DDOWNLOAD_BOOST_TIMEOUT=60000     # 下载boost的超时时间
-DWITH_BOOST=boost                 # boost目录,不存在时会自动下载到该目录
-DCMAKE_INSTALL_PREFIX=build_out   # MySQL安装目录,可在启动时指定`--basedir`覆盖
-DMYSQL_DATADIR=build_out/data     # MySQL数据目录,可在启动时指定`--datadir`覆盖
-DSYSCONFDIR=build_out/etc         # `my.cnf`默认目录,可在启动时指定`--defaults-file=file_name`覆盖
-DMYSQL_TCP_PORT=3307              # 如果本机已安装过MySQL,避免冲突换个别的
-DMYSQL_UNIX_ADDR=mysql-debug.sock # 默认/tmp/mysql.sock,避免冲突,此处相对与`--datadir`目录会自动创建

手动创建配置中目录

# 为何加`cmake-build-debug`前缀,因为`CLion`中`CMake`的`Build directory`就是`cmake-build-debug`,可自行修改
mkdir -p cmake-build-debug/build_out cmake-build-debug/build_out/data cmake-build-debug/build_out/etc
  1. 运行CMake View>Tool Windows>CMake>Reset Cache and Reload Project
  2. 编译 mysqld
  3. 初始化 mysqld 找到 mysqldProgram arguments 配置内容
--initialize-insecure
  1. 运行mysqld后,删除--initialize-insecure
  2. 开始debug

MySQL源码

推荐阅读:PolarDB 数据库内核月报

insert语句的执行过程

MySQL · 源码分析 · 一条insert语句的执行过程

InnoDB隐式锁

MySQL · 引擎特性 · InnoDB隐式锁功能解析

www.aneasystone.com/archives/20…

InnoDB mini transation

MySQL · 引擎特性 · InnoDB mini transation


相关实践学习
如何在云端创建MySQL数据库
开始实验后,系统会自动创建一台自建MySQL的 源数据库 ECS 实例和一台 目标数据库 RDS。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
2月前
|
SQL 关系型数据库 MySQL
遇到mysql数据库死锁,你会怎么排查?
遇到mysql数据库死锁,你会怎么排查?
142 0
|
1天前
|
SQL 关系型数据库 MySQL
案例剖析:MySQL唯一索引并发插入导致死锁!
案例剖析:MySQL唯一索引并发插入导致死锁!
案例剖析:MySQL唯一索引并发插入导致死锁!
|
15天前
|
监控 关系型数据库 MySQL
MySQL锁机制与解决死锁问题
MySQL锁机制与解决死锁问题
53 5
|
1天前
|
SQL 关系型数据库 MySQL
案例剖析,MySQL共享锁引发的死锁问题!
案例剖析,MySQL共享锁引发的死锁问题!
|
2月前
|
存储 SQL 关系型数据库
深入MySQL锁机制:原理、死锁解决及Java防范技巧
深入MySQL锁机制:原理、死锁解决及Java防范技巧
|
2月前
|
SQL JavaScript 关系型数据库
Mysql索引不当引发死锁问题
本文通过真实案例解析了MySQL在高并发环境下出现死锁的问题。数据库表`t_award`包含多个索引,但在执行特定SQL语句时遭遇索引失效,导致更新操作变慢并引发死锁。分析发现,联合索引`(pool_id, identifier, status, is_redeemed)`因`identifier`允许为空值而导致索引部分失效。此外,`pool_id`上的普通索引产生的间隙锁在高并发下加剧了死锁风险。为解决此问题,文中提出了调整索引顺序至`(pool_id, status, is_redeemed, identifier)`等方案来优化索引使用,进而减轻死锁现象。
|
2月前
|
Oracle 关系型数据库 MySQL
Mysql和Oracle数据库死锁查看以及解决
【8月更文挑战第11天】本文介绍了解决MySQL与Oracle数据库死锁的方法。MySQL可通过`SHOW ENGINE INNODB STATUS`查看死锁详情,并自动回滚一个事务解除死锁;也可手动KILL事务。Oracle则通过查询V$LOCK与V$SESSION视图定位死锁,并用`ALTER SYSTEM KILL SESSION`命令终止相关会话。预防措施包括遵循ACID原则、优化索引及拆分大型事务。
|
2月前
|
监控 关系型数据库 MySQL
MySQL死锁是什么
【8月更文挑战第26天】MySQL死锁是指两个或多个事务在执行过程中,因争夺锁资源而造成的相互等待的现象,若无外力干涉,它们都将无法继续执行。这种相互等待的情况会导致整个系统陷入停滞状态,影响数据库的性能和稳定性。
45 0
|
3月前
|
SQL 存储 关系型数据库
细说 MySQL 死锁
死锁检查在MySQL 8.0中涉及三个主要步骤:构造锁等待图、初始化事务权重和提升权重。首先,当事务进入锁等待状态时,信息会被记录到内存中的`waiting_threads`,形成快照数组。接着,对这个数组进行排序,构造出锁等待图,表示事务间的等待关系。然后,初始化所有等待事务的权重为1,如果一个事务在其他事务等待后进入等待,其权重会被提升,以避免长时间等待。最后,根据锁等待图,提升那些同时阻塞其他事务的权重,但不包括参与死锁的事务。权重更新后,死锁检查线程将依据这些信息来检测和解决死锁。
73 15
|
3月前
|
SQL 算法 关系型数据库
(十)全解MySQL之死锁问题分析、事务隔离与锁机制的底层原理剖析
经过《MySQL锁机制》、《MySQL-MVCC机制》两篇后,咱们已经大致了解MySQL中处理并发事务的手段,不过对于锁机制、MVCC机制都并未与之前说到的《MySQL事务机制》产生关联关系,同时对于MySQL锁机制的实现原理也未曾剖析,因此本篇作为事务、锁、MVCC这三者的汇总篇,会在本章中补全之前空缺的一些细节,同时也会将锁、MVCC机制与事务机制之间的关系彻底理清楚。