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

NestJS中MySQL SELECT...FOR UPDATE行锁未生效问题排查

问题根因

行锁不生效的核心原因是:你手动创建queryRunner启动的事务,和Repository层执行FOR UPDATE查询的数据库连接不是同一个。

TypeORM中调用getConnection().createQueryRunner()会从连接池申请一个独立的数据库连接,在这个连接上手动启动事务后,所有需要加入事务的SQL必须显式通过该queryRunner实例执行。而你在Repository中直接调用this.query()时,会从连接池申请另一个独立连接执行SQL,这个连接默认开启自动提交:SELECT ... FOR UPDATE执行完成后连接会自动提交,行锁被立刻释放,根本不会等到你调用queryRunner.commitTransaction()才释放,自然无法实现跨服务的互斥锁效果。

修复方案

核心原则:事务内所有SQL操作必须使用启动事务的同一个queryRunner连接执行,具体修改如下:

  • Service层启动事务后,将queryRunner实例传递给Repository层的所有操作方法
  • Repository层所有SQL(包括加锁查询、状态更新)都通过传入的queryRunner执行
  • 补充异常回滚、连接释放逻辑,避免事务悬挂、连接泄漏

修改后的代码示例:

// update-crawler.service.ts
async updateState() {
    const queryRunner = getConnection().createQueryRunner();
    await queryRunner.startTransaction();
    try {
      // 传入queryRunner,确保加锁查询走事务连接
      const updateStateJobs = await this.bridgeTransactionStateRepository.getUpdateCrawlingJob(queryRunner);
      for (const updateStateJob of updateStateJobs) {
        // 更新操作同样走同一个事务连接
        await this.bridgeTransactionStateRepository.updateState(txId, nextState, queryRunner);
      }
      await queryRunner.commitTransaction();
    } catch (err) {
      // 异常时回滚事务
      await queryRunner.rollbackTransaction();
      throw err;
    } finally {
      // 释放连接回连接池
      await queryRunner.release();
    }
  }
// bridge-transaction-state.repository.ts
import { QueryRunner } from 'typeorm';

async getUpdateCrawlingJob(queryRunner: QueryRunner): Promise<UpdateStateJobVo[]> {
    // 通过传入的queryRunner执行加锁查询,SQL在事务连接上运行
    const updateStateJobList: UpdateStateJobVo[] = await queryRunner.query(
      `SELECT * FROM bridge_transaction_state as bridge WHERE bridge.state IN ('TRANSFER_SUCCESS') FOR UPDATE;`,
    );
    return updateStateJobList;
}

async updateState(txId: string, nextState: string, queryRunner: QueryRunner) {
  // 更新操作使用同一个queryRunner执行
  await queryRunner.query(
    `UPDATE bridge_transaction_state SET state = ? WHERE tx_id = ?`,
    [nextState, txId]
  );
}
额外注意事项
  • 必须给bridge_transaction_state表的state字段创建索引,否则WHERE state IN ('TRANSFER_SUCCESS')会触发全表扫描,FOR UPDATE会锁住整张表的所有记录,而非只锁定符合条件的行,会严重影响并发性能
  • 表引擎必须使用InnoDB,MyISAM引擎不支持事务和行级锁,FOR UPDATE不会生效
  • 事务隔离级别使用InnoDB默认的REPEATABLE READ即可,无需额外调整
  • 不要在持有锁的事务内执行RPC调用、耗时计算等慢操作,会拉长锁持有时间,降低系统吞吐量

内容的提问来源于stack exchange,提问作者김도영 Dara

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 23:57:17