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

Node.js使用mysql2连接池实现INSERT/UPDATE事务如何避免死锁

解决方案

1. 优先使用MySQL原生UPSERT语法代替手动事务判断

你之前先查再写的逻辑本身就容易产生并发冲突,直接用INSERT ... ON DUPLICATE KEY UPDATE语法,单条SQL自带原子性,完全不需要手动开启事务,从根源避免死锁。
使用前提是你需要对判断记录存在性的字段添加唯一索引,比如你用id、user_id这类字段判断记录是否存在,先给该字段创建唯一索引即可,SQL示例:

INSERT INTO 表名 (字段1, 字段2, 判重唯一字段) VALUES (?, ?, ?)
ON DUPLICATE KEY UPDATE 字段1=VALUES(字段1), 字段2=VALUES(字段2);

Node.js中调用的代码示例:

const [result] = await pool.execute(sql, [val1, val2, uniqueVal]);

这种方式是单条原子SQL,不会出现事务中断导致的死锁问题,性能也比手动开启事务高很多。

2. 必须使用手动事务的优化方案

如果业务逻辑复杂无法用上述单条SQL实现,按以下规则调整你的事务代码:

  • 从连接池获取连接后,所有事务操作全程复用同一个连接,不要跨连接执行SQL,多数死锁问题都是因为事务内的查询和更新用了不同的连接导致的
  • 查询时加FOR UPDATE行锁,避免并发场景下多个事务同时查到同一条记录不存在,同时触发INSERT导致锁冲突:
SELECT * FROM 表名 WHERE 判重字段 = ? FOR UPDATE;
  • 严格控制事务执行时长,事务内不要放入任何外部IO操作(比如调用第三方接口、读写本地文件等),避免事务长时间挂起持有锁
  • 所有异常场景必须加回滚逻辑,不管是SQL执行报错还是代码逻辑报错,都要主动调用rollback释放连接和锁,禁止出现异常后直接抛错不回滚的情况
  • 给MySQL设置合理的事务超时时间,避免异常中断的事务长时间持有锁

代码示例:

const conn = await pool.getConnection();
try {
  await conn.beginTransaction();
  // 查询加行锁
  const [rows] = await conn.execute('SELECT * FROM 表名 WHERE unique_key = ? FOR UPDATE', [uniqueVal]);
  if (rows.length > 0) {
    await conn.execute('UPDATE 表名 SET 字段1 = ? WHERE unique_key = ?', [val1, uniqueVal]);
  } else {
    await conn.execute('INSERT INTO 表名 (字段1, unique_key) VALUES (?, ?)', [val1, uniqueVal]);
  }
  await conn.commit();
} catch (err) {
  // 异常必须回滚
  await conn.rollback();
  throw err;
} finally {
  // 无论成功失败都释放连接回连接池
  conn.release();
}

3. 死锁排查补充方向

如果调整后仍存在死锁,可开启MySQL死锁日志定位具体冲突来源:

SET GLOBAL innodb_print_all_deadlocks = 1;

查看死锁日志确认具体的锁冲突场景,按照上述两种方案调整基本都能解决。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 01:36:07