You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MySQL并发执行INSERT+UPDATE事务时出现插入阶段死锁求助

MySQL并发事务死锁问题排查与解决

问题描述

我有一个API,执行包含以下操作的事务:

INSERT INTO tbl VALUES (...)
UPDATE tbl SET col=val WHERE id=$(aboverow)

流程为先插入一条带自增主键的新行,通过Node.js的Sequelize ORM获取主键后生成val,再执行UPDATE更新对应列。

当发起5-6次并发API调用时,会有2-3次报错:

Deadlock found when trying to get lock; try restarting transaction

我原本认为死锁仅会在事务互相干扰时出现,但这里每个事务操作的都是独立新行,不应产生死锁。通过日志排查发现,死锁一致发生在INSERT步骤。

补充表结构

CREATE TABLE `subscription_invoice` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `userId` int(11) NOT NULL,
  `invoiceId` varchar(200) CHARACTER SET utf8 NOT NULL COMMENT '{key}',
  ...
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=81 DEFAULT CHARSET=latin1

注:invoiceId列由id和userId计算得出,因id是数据库自增生成,所以需要先插入行再更新invoiceId,整个操作在事务中完成。

问题原因分析

InnoDB的锁机制是核心诱因:

  • 并发插入自增主键行时,InnoDB会使用自增锁(AUTO-INC Lock),这是一种特殊表级锁。对于简单INSERT语句,锁会在语句执行后释放,但事务中后续的UPDATE操作会持有行锁,可能导致锁持有顺序交叉,触发死锁。
  • 即使操作独立新行,InnoDB的间隙锁(Gap Lock)可能在插入时锁定主键索引的间隙,并发事务的间隙锁范围若重叠,也会引发死锁。

解决方法

1. 调整自增锁模式

将InnoDB自增锁模式设置为innodb_autoinc_lock_mode=2(连续模式),该模式下自增锁仅在分配自增值时短暂持有,语句执行后立即释放,大幅降低锁冲突概率。

  • 配置方式:在my.cnf/my.ini中添加innodb_autoinc_lock_mode=2,重启MySQL服务(MySQL 8.0默认已启用该模式)。

2. 避免先插后更,直接生成invoiceId

跳过先插后更的流程,提前计算invoiceId后一次性插入:

  • 先获取当前自增主键值:
    SELECT AUTO_INCREMENT FROM information_schema.TABLES 
    WHERE TABLE_SCHEMA='你的数据库名' AND TABLE_NAME='subscription_invoice'
    
  • 用该值结合userId生成invoiceId,再执行INSERT语句插入所有字段。
  • 注意:需用事务包裹SELECT和INSERT,或使用GET_LOCK()保证原子性,避免并发下自增值被抢占。

3. 优化事务逻辑,缩小锁持有时长

尽量缩短事务执行时间,减少锁的持有周期:

INSERT INTO subscription_invoice (userId, ...) VALUES (?, ...);
SET @last_id = 617659;
UPDATE subscription_invoice SET invoiceId = CONCAT(@last_id, '-', ?) WHERE id = @last_id;

确保上述语句在同一事务中,且中间不加入耗时的业务逻辑,快速提交事务。

4. 应用层添加死锁重试机制

在Sequelize中捕获死锁错误,自动重试整个事务:

async function createInvoice(userId) {
  const retryLimit = 3;
  let attempt = 0;
  while (attempt < retryLimit) {
    const transaction = await sequelize.transaction();
    try {
      const invoice = await SubscriptionInvoice.create({ userId }, { transaction });
      const invoiceId = `${invoice.id}-${userId}`;
      await invoice.update({ invoiceId }, { transaction });
      await transaction.commit();
      return invoice;
    } catch (err) {
      await transaction.rollback();
      if (err.message.includes('Deadlock found') && attempt < retryLimit - 1) {
        attempt++;
        continue;
      }
      throw err;
    }
  }
}

内容的提问来源于stack exchange,提问作者Kushagra Gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.10 02:20:20