MySQL:死锁一例

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: 一、问题由来这是我同事问我的一个问题,在网上看到了如下案例,本案例RC RR都可以出现,其实这个死锁原因也比较简单,我们来具体看看:构造数据CREATE database deadlock_test;use deadlock_test;CREATE TABLE `push_token`...

一、问题由来

这是我同事问我的一个问题,在网上看到了如下案例,本案例RC RR都可以出现,其实这个死锁原因也比较简单,我们来具体看看:

构造数据
CREATE database deadlock_test;
use deadlock_test;
CREATE TABLE `push_token` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `token` varchar(128) NOT NULL COMMENT 'push token',
  `app_id` varchar(128) DEFAULT NULL COMMENT 'appid',
  `deleted` tinyint(1) NOT NULL COMMENT '是否已删除 0:否 1:是',
   PRIMARY KEY (`id`),
   UNIQUE KEY `uk_token_appid` (`token`,`app_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3384 DEFAULT CHARSET=utf8 COMMENT='pushtoken表';

insert into push_token (id, token, app_id, deleted) values(1,"token1",1,0);
操作数据

|s1(TRX_ID367661)|s2(TRX_ID367662)|s3(TRX_ID367663)|
|-|-|-|
|begin; UPDATE push_token SET deleted = 1 WHERE token = 'token1' AND app_id = '1';|||
||begin; DELETE FROM push_token WHERE id IN (1);||
|||begin; UPDATE push_token SET deleted = 1 WHERE token = 'token1' AND app_id = '1';|
|commit;|||
|Query OK, 0 rows affected (0.00 sec)| Query OK, 1 row affected (17.32 sec)| ERROR 1213 (40001): Deadlock found when trying to get lock; try restarting transaction|

二、分析方法

我使用的分析方法是把整个加锁的日志打印出来,当然需要用到我自己做了输出修改的一个版本,如下:

https://github.com/gaopengcarl/percona-server-locks-detail-5.7.22

这个版本我打开了的日志记录参数如下:

mysql> show variables like '%gaopeng%';
+--------------------------------+-------+
| Variable_name                  | Value |
+--------------------------------+-------+
| gaopeng_mdl_detail             | OFF   |
| innodb_gaopeng_row_lock_detail | ON    |
+--------------------------------+-------+
2 rows in set (0.01 sec)

这样大部分的Innodb加锁记录都会记录到errlog日志了。好了下面我详细分析一下日志:

三、分析过程

初始化的情况整个表只有1条记录,本表包含一个主键和一个唯一键。

1. s1(TRX_ID367661) 执行语句

begin;
UPDATE push_token SET deleted = 1 WHERE token = 'token1' AND app_id = '1';

日志输出:

2019-08-18T19:10:05.117317+08:00 6 [Note] InnoDB: TRX ID:(367661) table:deadlock_test/push_token index:uk_token_appid space_id: 449 page_id:4 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 6; hex 746f6b656e31; asc token1;;
 1: len 1; hex 31; asc 1;;
 2: len 8; hex 8000000000000001; asc         ;;
2019-08-18T19:10:05.117714+08:00 6 [Note] InnoDB: TRX ID:(367661) table:deadlock_test/push_token index:PRIMARY space_id: 449 page_id:3 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 8; hex 8000000000000001; asc         ;;
 1: len 6; hex 000000059c2c; asc      ,;;
 2: len 7; hex bf000000420110; asc     B  ;;
 3: len 6; hex 746f6b656e31; asc token1;;
 4: len 1; hex 31; asc 1;;
 5: len 1; hex 80; asc  ;;

我们看到主键和唯一键都加锁了,模式为LOCK_X|LOCK_NOT_GAP|如下图:

image.png

并且这个时候数据实际上是标记删除状态。

2. s2(TRX_ID367662) 执行语句

begin;DELETE FROM push_token WHERE id IN (1);

日志输出:

2019-08-18T19:10:22.751467+08:00 9 [Note] InnoDB: TRX ID:(367662) table:deadlock_test/push_token index:PRIMARY space_id: 449 page_id:3 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 8; hex 8000000000000001; asc         ;;
 1: len 6; hex 000000059c2d; asc      -;;
 2: len 7; hex 400000002a1dc8; asc @   *  ;;
 3: len 6; hex 746f6b656e31; asc token1;;
 4: len 1; hex 31; asc 1;;
 5: len 1; hex 81; asc  ;;
2019-08-18T19:10:22.752753+08:00 9 [Note] InnoDB: Trx(367662) is blocked!!!!!

这个时候S2需要获取主键上的:LOCK_X|LOCK_NOT_GAP| 锁,因此被堵塞了如下图:

image.png

3. s3(TRX_ID367663) 执行语句

begin; UPDATE push_token SET deleted = 1 WHERE token = 'token1' AND app_id = '1';

日志输出:

019-08-18T19:10:30.822111+08:00 8 [Note] InnoDB: TRX ID:(367663) table:deadlock_test/push_token index:uk_token_appid space_id: 449 page_id:4 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 6; hex 746f6b656e31; asc token1;;
 1: len 1; hex 31; asc 1;;
 2: len 8; hex 8000000000000001; asc         ;;
2019-08-18T19:10:30.918248+08:00 8 [Note] InnoDB: Trx(367663) is blocked!!!!!

这个时候S3需要获取唯一键上的LOCK_X|LOCK_NOT_GAP 锁,因此被堵塞了如下图:

image.png

4. s1(TRX_ID367661) 执行语句

这一步完成后死锁出现。

commit;

日志输出如下:

367663和367662各自获取需要的锁

2019-08-18T19:10:36.566733+08:00 8 [Note] InnoDB: TRX ID:(367663) table:deadlock_test/push_token index:uk_token_appid space_id: 449 page_id:4 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 6; hex 746f6b656e31; asc token1;;
 1: len 1; hex 31; asc 1;;
 2: len 8; hex 8000000000000001; asc         ;;
2019-08-18T19:10:36.568711+08:00 9 [Note] InnoDB: TRX ID:(367662) table:deadlock_test/push_token index:PRIMARY space_id: 449 page_id:3 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 8; hex 8000000000000001; asc         ;;
 1: len 6; hex 000000059c2d; asc      -;;
 2: len 7; hex 400000002a1dc8; asc @   *  ;;
 3: len 6; hex 746f6b656e31; asc token1;;
 4: len 1; hex 31; asc 1;;
 5: len 1; hex 81; asc  ;;

367663获取主键锁堵塞、367662获取唯一键锁堵塞,死锁形成
2019-08-18T19:10:36.570313+08:00 8 [Note] InnoDB: TRX ID:(367663) table:deadlock_test/push_token index:PRIMARY space_id: 449 page_id:3 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 6; compact format; info bits 0
 0: len 8; hex 8000000000000001; asc         ;;
 1: len 6; hex 000000059c2d; asc      -;;
 2: len 7; hex 400000002a1dc8; asc @   *  ;;
 3: len 6; hex 746f6b656e31; asc token1;;
 4: len 1; hex 31; asc 1;;
 5: len 1; hex 81; asc  ;;
2019-08-18T19:10:36.571199+08:00 8 [Note] InnoDB: Trx(367663) is blocked!!!!!
2019-08-18T19:10:36.572481+08:00 9 [Note] InnoDB: TRX ID:(367662) table:deadlock_test/push_token index:uk_token_appid space_id: 449 page_id:4 heap_no:2 row lock mode:LOCK_X|LOCK_NOT_GAP|
PHYSICAL RECORD: n_fields 3; compact format; info bits 0
 0: len 6; hex 746f6b656e31; asc token1;;
 1: len 1; hex 31; asc 1;;
 2: len 8; hex 8000000000000001; asc         ;;
2019-08-18T19:10:36.573073+08:00 9 [Note] InnoDB: Transactions deadlock detected, dumping detailed information.

完成这一步 s1实际上释放了锁, 然后我们首先看到s2获取了主键上的LOCK_X|LOCK_NOT_GAP锁,s3获取了唯一键上的LOCK_X|LOCK_NOT_GAP 锁。但是随后s3获取主键上的LOCK_X|LOCK_NOT_GAP锁堵塞,s2获取唯一键上的LOCK_X|LOCK_NOT_GAP锁堵塞。因此死锁形成,如下图:

image.png

好了我们看到了死锁就这样出现。整个分析过程我们只要看到加锁的日志实际上很容易就分析得出来。


最后欢迎关注我的专栏《深入理解MySQL主从原理 32讲》:

微信图片_20190722102922.jpg
微信图片_20190722102954.jpg

相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助     相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
相关文章
|
1天前
|
关系型数据库 MySQL 中间件
【MySQL实战笔记】07 | 行锁功过:怎么减少行锁对性能的影响?-02 死锁和死锁检测
【4月更文挑战第19天】在高并发环境下,死锁发生在多个线程间循环等待资源时,导致无限期等待。MySQL中,死锁可通过`innodb_lock_wait_timeout`参数设置超时或`innodb_deadlock_detect`开启死锁检测来解决。默认的50s超时可能不适用于在线服务,而频繁检测会消耗大量CPU。应对热点行更新引发的性能问题,可以暂时关闭死锁检测(风险是产生大量超时),控制并发度,或通过分散记录减少锁冲突,例如将数据分拆到多行以降低死锁概率。
16 1
|
2月前
|
SQL 关系型数据库 MySQL
MySQL - 死锁的产生及解决方案
MySQL - 死锁的产生及解决方案
193 0
|
3月前
|
算法 关系型数据库 MySQL
MySQL相关(九)- 死锁的发生和避免
MySQL相关(九)- 死锁的发生和避免
25 0
|
3月前
|
SQL 关系型数据库 MySQL
MySQL事务原理分析(ACID特性、隔离级别、锁、MVCC、并发读异常、并发死锁以及如何避免死锁)
MySQL事务原理分析(ACID特性、隔离级别、锁、MVCC、并发读异常、并发死锁以及如何避免死锁)
86 1
|
4月前
|
关系型数据库 MySQL 调度
KnowStreaming系列教程第四篇——MySQL数据更新死锁问题
KnowStreaming系列教程第四篇——MySQL数据更新死锁问题
43 0
|
4月前
|
SQL 存储 关系型数据库
MySQL 意向共享锁、意向排他锁、死锁
MySQL 意向共享锁、意向排他锁、死锁
51 0
MySQL 意向共享锁、意向排他锁、死锁
|
9月前
|
SQL 监控 算法
MySQL锁定:死锁及其避免方法
MySQL锁定:死锁及其避免方法
|
5月前
|
SQL NoSQL 关系型数据库
Mysql Innodb死锁情况分析与归纳
Mysql Innodb死锁情况分析与归纳
51 0
|
11月前
|
SQL 关系型数据库 MySQL
MySQL - 锁等待及死锁初探
MySQL - 锁等待及死锁初探
122 0
|
6月前
|
存储 SQL 关系型数据库
MySQL 优化 index merge(索引合并)引起的死锁分析(强烈推荐)
生产环境出现死锁流水,通过查看死锁日志,看到造成死锁的是两条一样的update语句(只有where条件中的值不同),如下: