MySQL死锁及源码分析!

2023年 11月 1日 45.3k 0

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

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

表锁

表锁由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

  • 下载源码 官网下载或者github上下载github.com/mysql/mysql…
  • 配置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
    
  • 运行CMake View>Tool Windows>CMake>Reset Cache and Reload Project

  • 编译 mysqld

  • 初始化 mysqld 找到 mysqldProgram arguments 配置内容

    --initialize-insecure
    

    运行mysqld后,删除--initialize-insecure

  • 开始debug

  • MySQL源码

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

    insert语句的执行过程

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

    InnoDB隐式锁

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

    www.aneasystone.com/archives/20…

    InnoDB mini transation

    MySQL · 引擎特性 · InnoDB mini transation

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

    相关文章

    Oracle如何使用授予和撤销权限的语法和示例
    Awesome Project: 探索 MatrixOrigin 云原生分布式数据库
    下载丨66页PDF,云和恩墨技术通讯(2024年7月刊)
    社区版oceanbase安装
    Oracle 导出CSV工具-sqluldr2
    ETL数据集成丨快速将MySQL数据迁移至Doris数据库

    发布评论