乐趣区

关于java:生产上数据库死锁是该程序员祭天了

Hello,大家好,我是 Skow

浏览这篇文章之前,大家能够问问本人

  • 何为死锁?
  • Mysql 具备哪些锁?
  • Mysql 的锁模式兼容矩阵你是否分明?
  • 如何排查死锁问题?

如果你能够闭着眼睛答复进去这些问题的,那么就默默点赞来到👍🏻

如果你对下面的知识点,还有点含糊不清,那么这篇文章将会带你从一个实在业务场景动手,剖析死锁问题,心愿本文对你有所帮忙,Let’s go 🤨

业务背景

目前我司有两个零碎 A 零碎、B 零碎

A 零碎寄存着公司所有人员的信息

B 零碎须要日终定时从 A 零碎同步数据

人员已在 B 零碎中存在,则更新,不存在则插入

因人员信息过多,所以采取多线程形式同步人员数据

在验证代码的时候,😡测试人员火冒三丈的反馈,sync\_user 也就是咱们的同步人员的那张数据表打不开了

遇事莫慌,先甩锅运维,“小姐姐,莫急莫慌,必定是数据库系统出问题了 ”

通过运维和 DBA 的排查,其实罪魁祸首是开发

咱们的代码导致了这张表呈现了死锁,从而导致表打不开了

那,到底是为何产生了死锁?接下来咱们还原一下案发现场

案发还原

看一下原始的建表语句(当然不会给你看实在的表)

CREATE TABLE `sync_user` (`user_id` VARCHAR ( 32) NOT NULL COMMENT '用户 ID',
`user_name` VARCHAR (32) DEFAULT NULL COMMENT '用户姓名',
`login_account` VARCHAR (50) DEFAULT NULL COMMENT '登陆账号',
PRIMARY KEY (`user_id`),
KEY `idx_login_account` (`login_account`) USING BTREE 
) ENGINE = INNODB DEFAULT CHARSET = utf8mb4 COMMENT = '用户信息表';

当初零碎中有张三、李四两个用户

表曾经准备就绪了,接下来看下咱们的数据隔离级别、并且把咱们的主动提交敞开

正戏开始!!!✍️

依照下图模仿下咱们并发同步数据的状况

  • 开启 事务 1, 执行更新语句 >UPDATE sync\_user SET user\_name = “ 张三 2 ” where login\_account = “zhangsan”;
  • 开启 事务 2, 执行更新语句 > UPDATE sync\_user SET user\_name = “ 李四 2 ” where login\_account = “lisi”; 更新胜利
  • 回到事务 1,执行插入语句 > INSERT INTO sync\_user (user\_id, user\_name, login\_account) VALUES (‘3’, ‘ 王五 ’, ‘wangwu’);– 此条语句阻塞中
  • 回到事务 2,执行插入语句 > INSERT INTO sync\_user (user\_id, user\_name, login\_account) VALUES (‘4’, ‘ 杨六 ’, ‘yangliu’); — 呈现死锁,并且事务 1 的插入语句执行胜利

以上就是咱们模仿的并发状况,课代表总结图如下 👇

死锁剖析

通过事务 2 提醒的 Deadlock found when trying to get lock; try restarting transaction

咱们能够很明确的失去,这就是产生了死锁状况

那么,什么是死锁呢?

大学老师都是这样通知咱们的:死锁是指多个过程在运行过程中因抢夺资源而造成的一种僵局,当过程处于这种僵持状态时,若无外力作用,它们都将无奈再向前推动 造成死锁的四个必要条件

  • 互斥条件:一个资源每次只能被一个过程应用;
  • 申请与放弃条件:一个过程因申请资源而阻塞时,对已取得的资源放弃不放;
  • 不剥夺条件: 过程已取得的资源,在末应用完之前,不能强行剥夺;
  • 循环期待条件: 若干过程之间造成一种头尾相接的循环期待资源关系;

ok,什么是死锁和造成死锁的必要条件咱们曾经晓得了,要产生死锁,必先有锁,那么 Mysql 有哪些锁呢?

Mysql 依照锁模式辨别有:记录锁、gap 锁、next-key 锁、插入意向锁

具体的锁作用篇幅限度,就不开展阐明

锁的兼容矩阵为:横行为以后曾经持有的锁,纵向为正在申请的锁

接下来,正式剖析一下事务 1、事务 2 各自拿到了什么锁

  • 事务 1 在更新 zhangsan 张三的时候
  • 间隙锁:UPDATE 语句会在非惟一索引的 login\_account 加上间隙锁,即取得 (lisi,zhangsan)、(zhangsan,+∞)
  • 记录锁:因为 login\_account 为索引,会在 zhangsan 这一行加锁
  • Next-Key 锁:Next-Key 锁 = 记录锁 + 间隙锁,所以该 UPDATE 语句就有了 (lisi,zhangsan] 的 Next-Key 锁
  • 综上所述:更新张三的语句取得了
  • Next-Key 锁 -> (lisi,zhangsan]
  • Gap 锁  -> (zhangsan,+∞)
  • 事务 1 在插入 wangwu 王五的时候
  • 间隙锁:因为 wangwu(在 lisi 和 zhangsan 之间),所以须要申请加 (lisi,zhangsan) 的间隙锁
  • 插入意向锁(Insert Intention):插入意向锁是在插入一行记录操作之前设置的一种间隙锁,这个锁开释了一种插入方式的信号,即事务 A 须要插入意向锁 (lisi,zhangsan)

因而,事务 1 的 UPDATE 语句和 INSERT 语句执行完,它是持有了 (lisi,zhangsan] 的 Next-Key 锁,(zhangsan,+∞) 的 Gap 锁,想拿到  (lisi,zhangsan) 的插入动向排它锁

事务 2 的剖析也如上举例,咱们间接给出答案

事务 2 的 UPDATE 语句和 INSERT 语句执行完,它是持有了 (-∞,lisi] 的 Next-Key 锁,(lisi,zhangsan) 的 Gap 锁,想拿到  (lisi,zhangsan) 的插入动向排它锁

锁曾经剖析结束了,接下来,咱们须要去查看一下事务的日志后果

假相行将浮出水面

事务 1 冀望拿到 (lisi,zhangsan) 的插入意向锁,然而这个范畴以后被事务 2 的 (lisi,zhangsan] 的 gap 锁占有了,这两把锁又是抵触的

事务 2 冀望拿到 (lisi,zhangsan) 的插入意向锁,然而这个范畴被事务 1 的 (lisi,zhangsan] 的 Next-Key 锁占有了,这两把锁又是抵触的

所以死锁产生。因为 Innodb 的底层机制,它会让其中一个事务让出资源,另外的事务执行胜利,这就是为什么你最初看到事务 1 插入胜利了,然而事务 2 的插入显示了 Deadlock found

总结

死锁起因曾经剖析进去了,那咱们当前面对死锁,整体解决思路是什么呢?

  • 甩锅运维
  • 模仿死锁场景
  • show engine innodb status; 查看死锁日志
  • 找出死锁 SQL
  • SQL 加锁剖析,这个能够去官网看哈
  • 剖析死锁日志(持有什么锁,期待什么锁)
  • 相熟锁模式兼容矩阵,InnoDB 存储引擎中锁的兼容性矩阵。

参考文章:

丁奇《MySql 实战 45 讲》

捡田螺的小男孩《手把手教你剖析 Mysql 死锁问题》


文章完结 🤣

如果本文对你有所帮忙的话,那就点个赞吧

更多分享尽在微信公众号【codeLiveHouse】

公众号回复“材料”能够获取大厂面试题 / 技术文档 / 电子书等等

退出移动版