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

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

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执行逻辑和锁的使用,是防止死锁的有效方式。

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

推荐文章

Python上下文管理器:with语句
2024-11-19 06:25:31 +0800 CST
Vue3中如何处理组件的单元测试?
2024-11-18 15:00:45 +0800 CST
一文详解回调地狱
2024-11-19 05:05:31 +0800 CST
Vue 3 中的 Fragments 是什么?
2024-11-17 17:05:46 +0800 CST
linux设置开机自启动
2024-11-17 05:09:12 +0800 CST
10个几乎无人使用的罕见HTML标签
2024-11-18 21:44:46 +0800 CST
Node.js中接入微信支付
2024-11-19 06:28:31 +0800 CST
如何在 Vue 3 中使用 TypeScript?
2024-11-18 22:30:18 +0800 CST
CSS 中的 `scrollbar-width` 属性
2024-11-19 01:32:55 +0800 CST
如何在Vue3中处理全局状态管理?
2024-11-18 19:25:59 +0800 CST
CentOS 镜像源配置
2024-11-18 11:28:06 +0800 CST
# 解决 MySQL 经常断开重连的问题
2024-11-19 04:50:20 +0800 CST
在 Rust 生产项目中存储数据
2024-11-19 02:35:11 +0800 CST
淘宝npm镜像使用方法
2024-11-18 23:50:48 +0800 CST
10个极其有用的前端库
2024-11-19 09:41:20 +0800 CST
ElasticSearch简介与安装指南
2024-11-19 02:17:38 +0800 CST
维护网站维护费一年多少钱?
2024-11-19 08:05:52 +0800 CST
程序员茄子在线接单