您当前的位置:首页 > 电脑百科 > 数据库 > MYSQL

手把手教你分析解决MySQL死锁问题

时间:2022-03-14 10:03:00  来源:  作者:Java码农之路
手把手教你分析解决MySQL死锁问题

 

在生产环境中如果出现MySQL死锁问题该如何排查和解决呢,本文将模拟真实死锁场景进行排查,最后总结下实际开发中如何尽量避免死锁发生。

一、准备好相关数据和环境

当前自己的数据版本是8.0.22

mysql> select @@version;
+-----------+
| @@version |
+-----------+
| 8.0.22    |
+-----------+
1 row in set (0.00 sec)

数据库隔离级别(默认隔离级别)

mysql> select @@transaction_isolation;
+-------------------------+
| @@transaction_isolation |
+-------------------------+
| REPEATABLE-READ         |
+-------------------------+
1 row in set (0.00 sec)

自动提交关闭

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            1 |
+--------------+
1 row in set (0.00 sec)

mysql> set autocommit=0;
Query OK, 0 rows affected (0.00 sec)

mysql> select @@autocommit;
+--------------+
| @@autocommit |
+--------------+
|            0 |
+--------------+
1 row in set (0.00 sec)

表结构

这个age为 非唯一的索引,这点对下面整个案例非常重要。

-- id是自增主键,age是非唯一索引,name普通字段
CREATE TABLE `user` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `age` int DEFAULT NULL COMMENT '年龄',
  `name` varchar(255)  DEFAULT NULL COMMENT '姓名',
  PRIMARY KEY (`id`),
  KEY `idx_age` (`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT='用户信息表';

表中暂时先插入两条数据

手把手教你分析解决MySQL死锁问题

 

二、模拟出真实死锁案例

开启两个终端模拟事务并发情况,执行顺序以及实验现象如下:

1)事务A执行更新操作,更新成功

mysql> update  user  set name = 'wangwu' where age= 20;
Query OK, 1 row affected (0.00 sec)
  1. 事务B执行更新操作,更新成功
mysql> update  user  set name = 'zhaoliu' where age= 10;
Query OK, 1 row affected (0.00 sec)

3)事务A执行插入操作,陷入阻塞~

mysql> insert into user values (null, 15, "tianqi");
手把手教你分析解决MySQL死锁问题

 

4)事务B执行插入操作,插入成功,同时事务A的插入由阻塞变为死锁error。

insert into user values (null, 30, "wangba");
Query OK, 1 row affected (0.00 sec)

事务A的插入操作变成报错。

手把手教你分析解决MySQL死锁问题

 

上面四步操作后,我们分别对事务A和事务B进行commit操作。

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

我们再来看数据库中表的数据。

手把手教你分析解决MySQL死锁问题

 

我们发现,事务B的所有操作最终都成功了,而事务A的操作因为报错都回滚了。所以事务A的操作都失败。

那既然是死锁,为什么回滚事务A,而不是事务B,是随机的还是有机制在里面?

我们可以理解死锁是数据库对事务的保护机制,一旦发生死锁,MySQL会选择相对小的事务(undo较少的)进行回滚

三、查看分析死锁日志

可以用 show engine innodb status,查看最近一次死锁日志哈,执行后,死锁日志如下(只展示部分日志):

LATEST DETECTED DEADLOCK
------------------------
2021-12-24 06:02:52 0x7ff7074f8700
*** (1) TRANSACTION:
TRANSACTION 2554368, ACTIVE 22 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2

INSERT INTO user VALUES (NULL, 15, "tianqi")

*** (1) HOLDS THE LOCK(S):
RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554368 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;;

*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554368 lock_mode X locks gap before rec insert intention waiting
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000014; asc     ;;
 1: len 4; hex 80000002; asc     ;;

*** (2) TRANSACTION:
TRANSACTION 2554369, ACTIVE 14 sec inserting
mysql tables in use 1, locked 1
LOCK WAIT 5 lock struct(s), heap size 1136, 4 row lock(s), undo log entries 2
INSERT INTO user VALUES (NULL, 30, "wangba")

*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554369 lock_mode X locks gap before rec
Record lock, heap no 4 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
 0: len 4; hex 80000014; asc     ;;
 1: len 4; hex 80000002; asc     ;;


*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 309 page no 5 n bits 72 index idx_age of table `mall_goods`.`user` trx id 2554369 lock_mode X insert intention waiting
Record lock, heap no 1 PHYSICAL RECORD: n_fields 1; compact format; info bits 0
 0: len 8; hex 73757072656d756d; asc supremum;;

*** WE ROLL BACK TRANSACTION (1)

1、事务A相关日志

1)找到关键词TRANSACTION,事务2554368

手把手教你分析解决MySQL死锁问题

 

2)查看事务1正在执行的sql

insert into user values (null, 15, "tianqi")
  1. 查看当前事务已占有的锁和等待其它事务释放的锁
手把手教你分析解决MySQL死锁问题

 

2、事务B相关日志

1)找到关键词TRANSACTION,事务2554369

手把手教你分析解决MySQL死锁问题

 

2)查看事务2正在执行的sql

insert into user values (null, 30, "wangba")
  1. 查看当前事务已占有的锁和等待其它事务释放的锁
手把手教你分析解决MySQL死锁问题

 

3、总结

这里把一些关键的日志截图了下来

手把手教你分析解决MySQL死锁问题

 

我们把这张图换一种方式画下来

手把手教你分析解决MySQL死锁问题

 

1)从图中可以很明显地看出,事务1和事务2都在等对方的锁释放,所以导致了死锁问题。而且最终是事务1进行了回滚。

2)这个日志提供比较重要的信息就是我们可以看出的是哪两条sql在互相一直等待其它事务的锁释放而产生了死锁,也知道是哪个索引导致产生的死锁,同时也知道最终哪个事务

被回滚了。

3)如果上面的信息还不能帮你定位解决问题,那可以问数据库DB要详细的binlog日志来分析这段时间这两个事务具体执行的所有sql。

四、总结分析案例中产生死锁的原因

这个分析就需要对MySQL中的各种锁机制有所了解,还不清楚的话可以看我之前写的两篇文章,看完你就清楚我下面所写的了。

  • 一文详解MySQL的锁机制
  • MySQL记录锁、间隙锁、临键锁小案例演示

1、事务A的SQL产生了哪些锁

1) 事务A的update语句产生哪些锁

我们先来看

update  user  set name = 'wangwu' where age= 20;

记录锁

因为是等值查询,所以这里会在满足age=20的所有数据请求一个记录锁。

间隙锁

因为这里是唯一索引的等值查询,所以一样会产生间隙锁(如果是唯一索引的等值查询那就不会产生间隙锁,只会有记录锁),因为这里只有2条记录

所以左边为(10,20),右边因为没有记录了,所以请求间隙锁的范围就是(20,+∞),加一起就是(10,20) +(20,+∞)。

Next-Key锁

Next-Key锁=记录锁+间隙锁,所以该Update语句就有了(10,+∞)的 Next-Key锁

事务A的install语句产生哪些锁

INSERT INTO user VALUES (NULL, 15, "tianqi");

间隙锁

因为age 15(在10和20之间),所以需要请求加入(10,20)的间隙锁

插入意向锁(Insert Intention)

插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁释放了一种插入方式的信号,即事务A需要插入意向锁(10,20),这个插入锁的作用就是提高插入效率的,在分析

死锁的时候我们可以不用关心它,只关心上面的间隙锁就好了。

2、事务B的SQL产生了哪些锁

事务B的update语句产生哪些锁

我们先来看

update  user  set name = 'zhaoliu' where age= 10

记录锁

因为是等值查询,所以这里会在满足age=10的所有数据请求一个记录锁。

间隙锁

因为左边没有记录,右边有一个age=20的记录,所以间隙锁的范围是(-∞,10),右边为(10,20),一起就是(-∞,10)+(10,20)。

Next-Key锁

Next-Key锁=记录锁+间隙锁,所以该Update语句就有了(-∞,20)的 Next-Key锁

事务A的install语句产生哪些锁

INSERT INTO user VALUES (NULL, 30, "wangba")

间隙锁

  • 因为age 30(左边是20,右边没有值),所以需要请求加(20,+∞)的间隙锁

插入意向锁(Insert Intention)

  • (20,+∞)

锁都分析清楚了,接下来再来看下是什么地方导致死锁的呢?

手把手教你分析解决MySQL死锁问题

 

这样一来产生整个死锁的原因也就清楚了,不过这里再补充两点

1)MySQL的间隙锁虽然有左开右闭的原则,但是其实这个并不完全正确,因为它有可能是左闭右开,也可能是左开右开,它会跟你插入主键值位置有关,具体的可以看我之前写的

一篇文章里面有完整示例MySQL记录锁、间隙锁、临键锁小案例演示。所以这里间隙锁写的都是左开右开的范围,可能临界点有点模糊,但不影响分析这个案例的死锁问题。

2)通过事务A和事务B的update语句,我们可以发现其实它们都持有间隙锁(10,20)的这段范围,说明间隙锁范围是可以相互兼容的,意思就是只要你的10不在我(10,+∞)的间隙锁

范围内,就可以产生部分重合的间隙锁,也就是这里的(10,20)。

五、实际开发中如何尽量避免死锁发生

一般来讲在实际开发中,很少会发生死锁的情况,尤其是在业务量不是很大的情况下。在并发很大的情况下可能会存在偶尔产生死锁。

不过呢,在自己实际开发中,有遇到过请求一个接口出现100%概率死锁的情况。

当时的场景其实很简单。一段业务代码中,有去走Dubbo调其它接口服务,这就存在了两个事务,结果各自事务提交的时候,都需要等待对方的锁释放,就导致每次都发生死锁超时。

这其实是一种代码不规范而导致死锁的发生。这里也总结下如何尽量避免死锁发生。

1)不同的应用访问同一组表时,应尽量约定以相同的顺序访问各组表。对一个表而言,应尽量以固定的顺序存取表中的信息。这点真的很重要,它可以明显的减少死锁的发生。

举例:好比有a,b两张表,如果事务1先a后b,事务2先b后a,那就可能存在相互等待产生死锁。那如果事务1和事务2都先a后b,那事务1先拿到a的锁,事务2再去拿a的锁,如果

锁冲突那就会等待事务1释放锁,那自然事务2就不会拿到b的锁,那就不会堵塞事务1拿到b的锁,这样就避免死锁了。

2)在主键等值更新的时候,尽量先查询看数据库中有没有满足条件的数据,如果不存在就不用更新,存在才更新。为什么要这么做呢,因为如果去更新一条数据库不存在的数据,

一样会产生间隙锁。

举例:如果表中只有id=1和id=5的数据,那么如果你更新id=3的sql,因为这条记录表中不存在,那就会产生一个(1,5)的间隙锁,但其实这个锁就是多余的,因为你去更新一个

数据都不存在的数据没有任何意义。

3)尽量使用主键更新数据,因为主键是唯一索引,在等值查询能查到数据的情况下只会产生行锁,不会产生间隙锁,这样产生死锁的概率就减少了。当然如果是范围查询,

一样会产生间隙锁。

4)避免长事务,小事务发送锁冲突的几率也小。这点应该很好理解。

5)在允许幻读和不可重复度的情况下,尽量使用RC的隔离级别,避免gap lock造成的死锁,因为产生死锁经常都跟间隙锁有关,间隙锁的存在本身也是在RR隔离级别来

解决幻读的一种措施。

感谢

这篇文章给自己提供了很好的思路,这篇文章也基本上按照这个思路往下写的



Tags:MySQL死锁   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,不构成投资建议。投资者据此操作,风险自担。如有任何标注错误或版权侵犯请与我们联系,我们将及时更正、删除。
▌相关推荐
MySQL死锁案例分析
一 背景死锁,其实是一个很有意思也很有挑战的技术问题,大概每个DBA和部分开发同学都会在工作过程中遇见 。本次分享的一个死锁案例是 涉及通过辅助索引的更新以及通过主键删除...【详细内容】
2023-01-10  Search: MySQL死锁  点击:(135)  评论:(0)  加入收藏
手把手教你分析解决MySQL死锁问题
在生产环境中如果出现MySQL死锁问题该如何排查和解决呢,本文将模拟真实死锁场景进行排查,最后总结下实际开发中如何尽量避免死锁发生。一、准备好相关数据和环境当前自己的数...【详细内容】
2022-03-14  Search: MySQL死锁  点击:(287)  评论:(0)  加入收藏
MySQL死锁产生的原因和解决方法
前言最近老顾经常碰到同事说,mysql又死锁了导致业务报错。今天我们就来聊聊死锁以及怎么解决锁类型mysql锁级别:页级、表级、行级表级锁:开销小,加锁快;不会出现死锁;锁定粒度大,发...【详细内容】
2021-09-06  Search: MySQL死锁  点击:(1288)  评论:(0)  加入收藏
MySQL死锁分析:记一次因索引合并导致的MySQL死锁分析过程
生产上偶现这段代码会出现死锁,死锁日志如下。*** (1) TRANSACTION:TRANSACTION 424487272, ACTIVE 0 sec fetching rowsmysql tables in use 3, locked 3LOCK WAIT 6 lock s...【详细内容】
2021-07-29  Search: MySQL死锁  点击:(264)  评论:(0)  加入收藏
Mysql死锁如何排查?insert on duplicate死锁排查过程分析
01 前言遇到Mysql死锁问题,我们应该怎么排查分析呢?之前线上出现一个insert on duplicate死锁问题,本文将基于这个死锁问题,分享排查分析过程,希望对大家有帮助。 02 死锁案发还...【详细内容】
2019-08-27  Search: MySQL死锁  点击:(973)  评论:(0)  加入收藏
▌简易百科推荐
MySQL 核心模块揭秘
server 层会创建一个 SAVEPOINT 对象,用于存放 savepoint 信息。binlog 会把 binlog offset 写入 server 层为它分配的一块 8 字节的内存里。 InnoDB 会维护自己的 savepoint...【详细内容】
2024-04-03  爱可生开源社区    Tags:MySQL   点击:(8)  评论:(0)  加入收藏
MySQL 核心模块揭秘,你看明白了吗?
为了提升分配 undo 段的效率,事务提交过程中,InnoDB 会缓存一些 undo 段。只要同时满足两个条件,insert undo 段或 update undo 段就能被缓存。1. 关于缓存 undo 段为了提升分...【详细内容】
2024-03-27  爱可生开源社区  微信公众号  Tags:MySQL   点击:(15)  评论:(0)  加入收藏
MySQL:BUG导致DDL语句无谓的索引重建
对于5.7.23之前的版本在评估类似DDL操作的时候需要谨慎,可能评估为瞬间操作,但是实际上线的时候跑了很久,这个就容易导致超过维护窗口,甚至更大的故障。一、问题模拟使用5.7.22...【详细内容】
2024-03-26  MySQL学习  微信公众号  Tags:MySQL   点击:(13)  评论:(0)  加入收藏
从 MySQL 到 ByteHouse,抖音精准推荐存储架构重构解读
ByteHouse是一款OLAP引擎,具备查询效率高的特点,在硬件需求上相对较低,且具有良好的水平扩展性,如果数据量进一步增长,可以通过增加服务器数量来提升处理能力。本文将从兴趣圈层...【详细内容】
2024-03-22  字节跳动技术团队    Tags:ByteHouse   点击:(28)  评论:(0)  加入收藏
MySQL自增主键一定是连续的吗?
测试环境:MySQL版本:8.0数据库表:T (主键id,唯一索引c,普通字段d)如果你的业务设计依赖于自增主键的连续性,这个设计假设自增主键是连续的。但实际上,这样的假设是错的,因为自增主键不...【详细内容】
2024-03-10    dbaplus社群  Tags:MySQL   点击:(13)  评论:(0)  加入收藏
准线上事故之MySQL优化器索引选错
1 背景最近组里来了许多新的小伙伴,大家在一起聊聊技术,有小兄弟提到了MySQL的优化器的内部策略,想起了之前在公司出现的一个线上问题,今天借着这个机会,在这里分享下过程和结论...【详细内容】
2024-03-07  转转技术  微信公众号  Tags:MySQL   点击:(31)  评论:(0)  加入收藏
MySQL数据恢复,你会吗?
今天分享一下binlog2sql,它是一款比较常用的数据恢复工具,可以通过它从MySQL binlog解析出你要的SQL,并根据不同选项,可以得到原始SQL、回滚SQL、去除主键的INSERT SQL等。主要...【详细内容】
2024-02-22  数据库干货铺  微信公众号  Tags:MySQL   点击:(53)  评论:(0)  加入收藏
如何在MySQL中实现数据的版本管理和回滚操作?
实现数据的版本管理和回滚操作在MySQL中可以通过以下几种方式实现,包括使用事务、备份恢复、日志和版本控制工具等。下面将详细介绍这些方法。1.使用事务:MySQL支持事务操作,可...【详细内容】
2024-02-20  编程技术汇    Tags:MySQL   点击:(54)  评论:(0)  加入收藏
MySQL数据库如何生成分组排序的序号
经常进行数据分析的小伙伴经常会需要生成序号或进行数据分组排序并生成序号。在MySQL8.0中可以使用窗口函数来实现,可以参考历史文章有了这些函数,统计分析事半功倍进行了解。...【详细内容】
2024-01-30  数据库干货铺  微信公众号  Tags:MySQL   点击:(55)  评论:(0)  加入收藏
mysql索引失效的场景
MySQL中索引失效是指数据库查询时无法有效利用索引,这可能导致查询性能显著下降。以下是一些常见的MySQL索引失效的场景:1.使用非前导列进行查询: 假设有一个复合索引 (A, B)。...【详细内容】
2024-01-15  小王爱编程  今日头条  Tags:mysql索引   点击:(87)  评论:(0)  加入收藏
站内最新
站内热门
站内头条