2024年最全教科书般的排查与分析过程,教你解决线上频出MySQL死锁问题!,60分钟快速掌握RabbitMQ

知其然不知其所以然,大厂常问面试技术如何复习?

1、热门面试题及答案大全

面试前做足功夫,让你面试成功率提升一截,这里一份热门350道一线互联网常问面试题及答案助你拿offer

2、多线程、高并发、缓存入门到实战项目pdf书籍

3、文中提到面试题答案整理

4、Java核心知识面试宝典

覆盖了JVM 、JAVA集合、JAVA多线程并发、JAVA基础、Spring原理、微服务、Netty与RPC、网络、日志、Zookeeper、Kafka、RabbitMQ、Hbase、MongoDB 、Cassandra、设计模式、负载均衡、数据库、一致性算法 、JAVA算法、数据结构、算法、分布式缓存、Hadoop、Spark、Storm的大量技术点且讲解的非常深入

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

mysql tables in use 1, locked 1
LOCK WAIT 4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 2
MySQL thread id 6264489, OS thread handle 140273305761536, query id 837446998 10.10.59.164 root updating
UPDATE test_table SET money = money + 5 WHERE user_id = 5
*** (1) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146580 lock_mode X locks rec but not gap waiting
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 8000000000000005; asc ;;
1: len 8; hex 8000000000000006; asc ;;

*** (2) TRANSACTION:
TRANSACTION 95146581, ACTIVE 2 sec starting index read
mysql tables in use 1, locked 1
4 lock struct(s), heap size 1136, 5 row lock(s), undo log entries 2
MySQL thread id 6264490, OS thread handle 140280327919360, query id 837446999 10.10.59.164 root updating
UPDATE test_table SET money = money + 4 WHERE user_id = 4
*** (2) HOLDS THE LOCK(S):
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap
Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 8000000000000005; asc ;;
1: len 8; hex 8000000000000006; asc ;;

Record lock, heap no 5 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 8000000000000001; asc ;;
1: len 8; hex 8000000000000002; asc ;;

*** (2) WAITING FOR THIS LOCK TO BE GRANTED:
RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting
Record lock, heap no 2 PHYSICAL RECORD: n_fields 2; compact format; info bits 0
0: len 8; hex 8000000000000004; asc ;;
1: len 8; hex 8000000000000005; asc ;;

*** WE ROLL BACK TRANSACTION (2)

关键点总结如下:

1.该库中最近一次死锁发生的时间是什么时候?

4行:2020-07-14 23:34:29 0x7f958f1d5700
得知,最近一次死锁发生在 2020-07-14 23:34:29

2.该次死锁导致的两个事务的重要信息?

12行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146580 lock_mode X locks rec but not gap waiting
得知,事务 1 等待的锁为:lock_mode X locks rec but not gap waiting

24行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap
得知,事务 2 持有的锁为:lock_mode X locks rec but not gap

34行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting
得知,事务 2 等待的锁为:lock_mode X locks rec but not gap waiting

39行:*** WE ROLL BACK TRANSACTION (2)
得知,最后回滚的是事务 1

从 12、24、34 行:index idx_user_id of table mall.test_table
得知:导致该次死锁的索引为:idx_user_id

3.能知道导致死锁的两个具体 SQL 吗?

不能,产生死锁的情况各式各样,事务中的 SQL 可能不止有两个 SQL,单从死锁日志是没法知道具体原因的,必须要结合业务代码查看事务上下文查看

2. 理论知识

排查过程中发现有个特点,影响的都是是线上的大用户。由于当时我很久没看死锁相关的理论知识,因此先去了解下相关死锁的基本知识。

2.1 死锁的条件

  1. 互斥条件:一个资源每次只能被一个进程使用。
  2. 占有且等待:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可强行占有:进程已获得的资源,在末使用完之前,不能强行剥夺。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

破坏死锁也很简单,四个条件破一个即可。(本案例是破坏的 4)

2.2 数据库的锁类型

数据库的死锁比较复杂,主要是由 Insert、Update(其实在开发中 Delete 或 For Update 是不怎么不考虑的,因为在实际业务代码中我们一般不会有 Delete 或 For Update 的操作,删除都是物理删除,for update 建议少用,除非你知道非用不可)。
InnoDB 的锁:

  1. 共享锁与独占锁(S、X)
  2. 意向锁
  3. 记录锁(Record Locks)
  4. 间隙锁(Gap Locks)
  5. Next-Key Locks
  6. 插入意向锁
  7. 自增锁
  8. 空间索引断言锁

这里参考了官网的 Innodb 锁分类,从死锁日志的 lock_mode X locks rec but not gap ,大致能知道,这里可能涉及了 X 锁、记录锁、间隙锁(但是有个 not,说明不涉及)。

3.  从死锁日志分析

分析之前先得到该表的建表语句:show create table test_table;

CREATE TABLE test_table (
id bigint(20) NOT NULL AUTO_INCREMENT,
money bigint(20) NOT NULL,
user_id bigint(20) NOT NULL,
PRIMARY KEY (id),
KEY idx_user_id (user_id)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8

接着结合死锁日志、锁的种类、建表语句得出以下模糊的结论:

1.从死锁日志的 10、12 行结合建表索引得知

10行:UPDATE test_table SET money = money + 5 WHERE user_id = 5
12行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146580

事务1的 UPDATE test_table SET money = money + 5 WHERE user_id = 5  语句在等待锁:它通过普通索引 idx_user_id 更新,先获取了 user_id=5 的 X 锁,接着去申请对应行的主键(Record Lock)的行锁但是被阻塞(waiting),并不包括间隙锁(not gap)。具体是哪个主键我们并不清楚。

2.从死锁日志的 22、24 行结合建表索引得知

22行:UPDATE test_table SET money = money + 4 WHERE user_id = 4
24行:Record lock, heap no 3 PHYSICAL RECORD: n_fields 2; compact format; info bits 0

事务2的 UPDATE test_table SET money = money + 4 WHERE user_id = 4  语句在持有锁:它通过普通索引 idx_user_id 更新,先获取了 user_id=4 的 X 锁,接着去申请对应行的主键(Record Lock)的行锁成功了,并不包括间隙锁(not gap)。具体是成功的哪个主键我们并不清楚。

3.从死锁日志的 22、34 行结合建表索引得知

22行:UPDATE test_table SET money = money + 4 WHERE user_id = 4
34行:RECORD LOCKS space id 71816 page no 4 n bits 80 index idx_user_id of table mall.test_table trx id 95146581 lock_mode X locks rec but not gap waiting

事务2的 UPDATE test_table SET money = money + 4 WHERE user_id = 4  语句在等待锁:它通过普通索引 idx_user_id 更新,先获取了 user_id=4 的 X 锁,接着去申请对应行的主键(Record Lock)的行锁但是被阻塞(waiting),并不包括间隙锁(not gap)。具体是哪个主键我们并不清楚。

模糊结论肯定是有问题的,最大的问题在于导致的 SQL 语句不正确,即:死锁的原因是真实的,但是具体是因为哪些 SQL 导致的死锁是不清楚的。接着我们整理得到了以下可能有问题的表格:

事务1事务2
某些 SQL某些 SQL
某个 SQL 的 user_id = 5 行更新操作被阻塞了某个 SQL 的 user_id = 4 获得了锁但是又阻塞了
某些 SQL某些 SQL

可以得知,其实单从死锁日志分析是比较片面的,因为 user_id 为 4、5 这两个 update 操作是不会有互相阻塞的问题,肯定是有别的 SQL 影响,我们需要额外从业务日志分析才能还原完整的现场。

4. 从业务日志分析

从死锁日志是不能完全知道导致的关键 SQL 和故障现场的整体流程,因此我们要借助业务日志来完成最后对故障现场的分析:
通过前面对业务日志的分析,我们知道最关键的调用方法是 BizManagerImpl.transactionMoney,查看对应源码:

@Override
@Transactional
public boolean transactionMoney(List transactionReqVOList) throws Exception {
for (TransactionReqVO transactionReqVO : transactionReqVOList) {
// 模拟业务操作
Thread.sleep(1000);
int updateCount = testTableService.update(transactionReqVO.getUserId(), transactionReqVO.getMoney());
if (updateCount == 0) {
log.error(“转账异常:” + transactionReqVO);
}
}
return true;
}

可以知道,应该是 for 循环事务的问题,但是具体是哪些 user_id 是不清楚的,接着我们查看业务日志的上下文,通过全链路 traceId(模拟) 做搜索,得到以下的日志:

[ConsumerThread2] org.example.controller.TestController : 全局链路跟踪id:2的日志:[TransactionReqVO(userId=4, money=4), TransactionReqVO(userId=2, money=2), TransactionReqVO(userId=5, money=5)]
[ConsumerThread1] org.example.controller.TestController : 全局链路跟踪id:1的日志:[TransactionReqVO(userId=5, money=5), TransactionReqVO(userId=1, money=1), TransactionReqVO(userId=4, money=4)]

分析到这一步,我们已经可以还原死锁场景了,事务流程图如下:

事务1事务2
UPDATE test_table SET money = money + 4 WHERE user_id = 4UPDATE test_table SET money = money + 5 WHERE user_id = 5
UPDATE test_table SET money = money + 2 WHERE user_id = 2UPDATE test_table SET money = money + 1 WHERE user_id = 1
UPDATE test_table SET money = money + 5 WHERE user_id = 5UPDATE test_table SET money = money + 4 WHERE user_id = 4

5. 业务日志、死锁日志结合分析

将死锁日志分析得出的不正确表格加上业务日志分析得出正确表格,我们得出最终带有理解的最终正确的事务表格:

事务1事务2
UPDATE test_table SET money = money + 4 WHERE user_id = 4 持有 user_id 为 4 的行锁UPDATE test_table SET money = money + 5 WHERE user_id = 5 持有 user_id 为 5 的行锁
UPDATE test_table SET money = money + 2 WHERE user_id = 2 持有 user_id 为 2 的行锁UPDATE test_table SET money = money + 1 WHERE user_id = 1 持有 user_id 为 1 的行锁
UPDATE test_table SET money = money + 5 WHERE user_id = 5

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

]

并发编程面试专题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值