编程 MySQL死锁 - 更新插入导致死锁

2024-11-19 05:53:50 +0800 CST views 464

MySQL死锁 - 更新插入导致死锁

1. 问题背景

前段时间,业务量大涨,团队需要扩展,招聘过程中经常遇到关于MySQL死锁的问题。许多面试中也涉及类似问题,例如如何分析MySQL死锁及其解决方案。本文将以一个实际案例来分析MySQL死锁的原因和解决方法。

2. 线上问题

2.1 线上异常日志

在错误日志中,我们发现死锁问题。通过排查业务代码,我们确定了问题:在业务逻辑中,先更新历史的回收单状态为失效,再插入新的回收单数据。如果并发执行类似操作,可能会导致死锁。

2.2 数据准备

在测试库中创建一个表,并插入测试数据。

CREATE TABLE `checkout_detail` (
  `id` bigint(20) NOT NULL COMMENT '主键id',
  `recycle_order_id` bigint(20) NOT NULL COMMENT '回收单ID',
  `confirm_recycle_time` datetime NOT NULL COMMENT '确认回收时间',
  `contrast_type` int(4) NOT NULL COMMENT '对比类型:1:售前、2:后验、3:售后',
  `remark` varchar(255) DEFAULT '' COMMENT '备注',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_idx_recycle_order_id_contrast_type` (`recycle_order_id`,`contrast_type`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='后验详情表';

插入几条测试数据:

INSERT INTO checkout_detail (id, recycle_order_id, confirm_recycle_time, contrast_type, remark) VALUES
(1, 1, '2024-07-15 19:56:01', 1, '回收单1'),
(2, 10, '2024-07-15 19:56:01', 2, '回收单10'),
(3, 20, '2024-07-15 19:56:01', 3, '回收单20');

2.3 问题复现

执行如下操作可以模拟死锁:

2.3.1 执行流程

执行顺序事务A事务B
STARTSTART TRANSACTION;START TRANSACTION;
1UPDATE checkout_detail SET remark = '更新状态' WHERE recycle_order_id = 30;
2UPDATE checkout_detail SET remark = '更新状态' WHERE recycle_order_id = 40;
3INSERT INTO checkout_detail (id, recycle_order_id, confirm_recycle_time, contrast_type, remark) VALUES(30, 30, '2024-07-15 19:56:01', 1, '插入回收单30');
4INSERT INTO checkout_detail (id, recycle_order_id, confirm_recycle_time, contrast_type, remark) VALUES(40, 40, '2024-07-15 19:56:01', 1, '插入回收单40');

2.3.2 死锁排查

执行第三步时,会发生锁等待,执行第四步时会发生死锁。我们可以使用以下SQL查看死锁日志:

SHOW ENGINE INNODB STATUS;

2.3.3 死锁日志分析

我们重点分析日志中锁的信息:

  • 事务A23087:在插入数据时,事务A持有针对uniq_idx_recycle_order_id_contrast_type索引的排他锁,但在等待一个插入意向锁。
  • 事务B23088:事务B与事务A的情况类似,它们在不同的索引位置上都持有插入意向锁,最终导致死锁。

3. 分析原因

通过对MySQL锁机制的理解,死锁主要由间隙锁和插入意向锁的冲突导致。间隙锁防止多个事务同时在同一范围内插入数据,而插入意向锁与间隙锁互斥,导致事务相互等待,最终形成死锁。

4. 解决方法

  1. 先查询再更新或插入:在执行插入操作之前,先查询数据是否存在。如果存在,则进行更新操作;如果不存在,再进行插入操作。这可以减少锁的范围,避免不必要的锁冲突。
  2. 避免大范围数据修改:尽量减少每次事务中修改的数据量,减少锁定的范围。
  3. 合理使用索引:确保SQL语句能够使用适当的索引,减少锁定的行数。

5. 总结

死锁的发生主要是由于多个事务对相同的资源(如索引或行)进行了交叉访问,导致了锁的冲突。通过优化事务的执行顺序、减少锁定的范围以及合理使用索引,可以有效避免死锁的发生。

两个事务即使生成的间隙锁范围是相同的,也不会冲突,但插入意向锁与间隙锁之间是互斥的。优化SQL执行逻辑和锁的使用,是防止死锁的有效方式。

复制全文 生成海报 数据库 性能优化 事务管理

推荐文章

Golang Sync.Once 使用与原理
2024-11-17 03:53:42 +0800 CST
Go 并发利器 WaitGroup
2024-11-19 02:51:18 +0800 CST
Vue中的样式绑定是如何实现的?
2024-11-18 10:52:14 +0800 CST
Python 获取网络时间和本地时间
2024-11-18 21:53:35 +0800 CST
Plyr.js 播放器介绍
2024-11-18 12:39:35 +0800 CST
Vue3中的Slots有哪些变化?
2024-11-18 16:34:49 +0800 CST
Vue3中的v-for指令有什么新特性?
2024-11-18 12:34:09 +0800 CST
20个超实用的CSS动画库
2024-11-18 07:23:12 +0800 CST
资源文档库
2024-12-07 20:42:49 +0800 CST
一键压缩图片代码
2024-11-19 00:41:25 +0800 CST
Grid布局的简洁性和高效性
2024-11-18 03:48:02 +0800 CST
php使用文件锁解决少量并发问题
2024-11-17 05:07:57 +0800 CST
防止 macOS 生成 .DS_Store 文件
2024-11-19 07:39:27 +0800 CST
程序员茄子在线接单