当前位置: 首页 > article >正文

【mysql】并发 Insert 的死锁问题 第二弹

上次死锁的场景还历历在目(【mysql】并发 Insert 的死锁问题:Deadlock found when trying to get lock; try restarting transaction_1213 - deadlock found when trying to get lock; try-CSDN博客),这次又把代码写死了

1. 问题

有 2 张数据库表:

users表: id(主键), name, age, gender, phone, create_t
user_relation表: user_id(外键), relation_id, relation_type唯一索引:(user_id, relation_id, relation_type)

在一个事务中,我的 sql 操作为:

-- 插入几条记录到users表
INSERT INTO users (id, name, age, gender, phone, create_t) VALUES('2931502437784617085','user1',20,0,14710002001,'2025-05-15 18:00:00'),('2931502437867716733','user2',20,0,14710002002,'2025-05-15 18:00:00'),('2931502437942362237','user3',20,0,14710002003,'2025-05-15 18:00:00'),...;-- 删除user_relation表中user_id的所有记录
DELETE FROM user_relation WHERE user_id IN ('2931502437784617085','2931502437867716733','2931502437942362237', ...);-- 插入几条记录到user_relation表
INSERT INTO user_relation (user_id, relation_id, relation_type) VALUES('2931502437784617085','2931502437814763645','1'),('2931502437867716733','2931502437913198717','1'),('2931502437942362237','2931502438050168957','1'),...;

运行时发现死锁概率极高,甚至并发量不大时(并发为 5,单次 insert 3 条时)还会出现死锁。

mysql 事务隔离级别为:RR

2. 分析死锁日志

SHOW ENGINE INNODB STATUS;

事务 1 持有 X 锁:

RECORD LOCKS space id 971 page no 1672 n bits 464 index userid_relationid_relationtype of table `user_relation_rel` trx id 2339976 lock_mode X locks gap before rec

事务 1 等待某个间隙上的插入意向锁:

RECORD LOCKS space id 971 page no 1672 n bits 440 index userid_relationid_relationtype of table `user_relation_rel` trx id 2339976 lock_mode X locks gap before rec insert intention waiting

事务 2 持有 X 锁:

RECORD LOCKS space id 971 page no 1672 n bits 464 index userid_relationid_relationtype of table `user_relation_rel` trx id 2339977 lock_mode X locks gap before rec

事务 2 等待某个间隙上的插入意向锁:

RECORD LOCKS space id 971 page no 1672 n bits 440 index userid_relationid_relationtype of table `user_relation_rel` trx id 2339977 lock_mode X locks gap before rec insert intention waiting

所以死锁原因为:

表上有唯一索引,在 insert 时会检查唯一性约束,mysql 会先获取间隙锁来防止幻读。当多个事务同时插入时,它们可能在竞争同一个索引页上的间隙锁,互相阻塞形成死锁。

3. 几种解决方法

(1) 调整索引

将 唯一索引  (user_id, relation_id, relation_type) 改为一个普通索引 (user_id)

  • 普通索引没有唯一性约束检查,减少了锁的竞争
  • 锁的范围变小(只会锁定user_id相关范围,而不是三个字段的组合)

结果:死锁情况略有缓解

(2) 修改事务隔离级别

将 RR 改为 RC:

SET SESSION TRANSACTION ISOLATION LEVEL READ COMMITTED;

结果:影响较大,未做修改

(3) 减小每批插入的数据量

结果:单条语句插入个数降到 1 个,只要并发请求还会死锁

(4) 增加重试

在应用代码中判断 mysql 的 error,当死锁时重试几次:

// TxExecWithRetry 执行一个事务函数, 在出现死锁时重试
//
// execFunc: 执行函数
// retryNum: 死锁重试次数
// backOff:  重试间隔(单位ms),按退避策略逐渐递增(50ms、100ms、200ms... 最大maxBackoff)
func TxExecWithRetry(db *database.DB, execFunc func(*sqlx.Tx) error, retryNum, backOff, maxBackoff int) error {var err errorretried := 0for retried <= retryNum {tx := db.MustBegin()err = execFunc(tx)if err == nil {return tx.Commit()} else if !IsDeadlockErr(err) {_ = tx.Rollback()return err}slog.Debug("tx exec deadlock, retry...", "retried", retried)time.Sleep(time.Duration(backOff+rand.Intn(20)) * time.Millisecond)backOff *= 2if backOff > maxBackoff {backOff = maxBackoff}retried++}return err
}func IsDeadlockErr(err error) bool {if err == nil {return false}var mysqlErr *mysql.MySQLErrorif errors.As(err, &mysqlErr) {return mysqlErr.Number == 1213}return false
}

结果:事务执行成功数增加,但是由于重试导致请求时间变长,部分事务甚至需要重试 5 次以上才能成功。

(5) 移去 DELETE 语句(关键!!)

DELETE 操作比 INSERT 操作会锁定更多的行和范围,这才是死锁的根本原因!

DELETE 锁定范围:

  • 记录锁(Record Lock):锁定要删除的每一行数据
  • 间隙锁(Gap Lock):锁定索引记录之间的间隙,防止其他事务插入新记录
  • Next-Key Lock:记录锁+间隙锁的组合,锁定记录及其前面的间隙
  • 二级索引的锁定:还会锁定相关二级索引条目

INSERT 锁定范围:

  • 插入意向锁(Insert Intention Lock):一种特殊的间隙锁,表示打算在某个间隙插入记录
  • 新插入记录上的排他锁:仅锁定新插入的行

所以可以优化 DELETE 语句,按业务需要,可以移去、或软删除、或减小删除范围。

结果:彻底解决

http://www.lryc.cn/news/2379497.html

相关文章:

  • Docker配置SRS服务器 ,ffmpeg使用rtmp协议推流+vlc拉流
  • 一个stm32工程从底层上都需要由哪些文件构成
  • [Mac] 开发环境部署工具ServBay 1.12.2
  • 商城小程序源码介绍
  • 鸿蒙OSUniApp 实现图片上传与压缩功能#三方框架 #Uniapp
  • 科技项目验收测试对软件产品和企业分别有哪些好处?
  • javascript和vue的不同
  • duxapp 2025-01-06更新 CLI新增帮助支持,优化基础模块结构
  • 汽车零部件冲压车间MES一体机解决方案
  • hysAnalyser 从MPEG-TS导出ES功能说明
  • 家里wifi不能上网或莫名跳转到赌博及色情网站就是域名被劫持、DNS被污染了
  • 基于SSM实现的健身房系统功能实现十六
  • 【Java微服务组件】分布式协调P1-数据共享中心简单设计与实现
  • [Harmony]大文件持久化
  • pgsql14自动创建表分区
  • cursor/vscode启动项目connect ETIMEDOUT 127.0.0.1:xx
  • Leetcode 3553. Minimum Weighted Subgraph With the Required Paths II
  • 兼顾长、短视频任务的无人机具身理解!AirVista-II:面向动态场景语义理解的无人机具身智能体系统
  • springboot踩坑记录
  • SparkSQL基本操作
  • Web 架构之动静分离
  • 20250515配置联想笔记本电脑IdeaPad总是使用独立显卡的步骤
  • sparkSQL读入csv文件写入mysql
  • 大涡模拟实战:从区域尺度到街区尺度的大气环境模拟
  • centos安装方式的aarch64架构下的kylinv10安装docker23.0.0
  • 单目测距和双目测距 bev 3D车道线
  • 鸿蒙OSUniApp 实现一个精致的日历组件#三方框架 #Uniapp
  • 【爬虫】DrissionPage-3
  • Web开发-JavaEE应用SpringBoot栈SnakeYaml反序列化链JARWAR构建打包
  • 项目复习(2)