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

NestJS+TypeORM 0.2事务异常排查与代码修复求助

问题:TypeORM事务日志显示回滚但数据未回滚,同时出现QueryRunnerAlreadyReleasedError

问题现象

  • 本地MySQL环境下,日志明确输出ROLLBACK,但a、b、c表及测试用的test_user、test_pg表中数据仍全部留存,未执行回滚
  • 测试时将cEntity设为undefined,触发如下错误:
"statusCode": 400,
"timestamp": "2023-12-18T15:45:00.000Z",
"path": "/api/v1/rsvcenter/inquiry/hall",
"message": "Cannot save, given value must be an entity, instead \"undefined\" is given."
  • 同时伴随异常:
QueryRunnerAlreadyReleasedError: Query runner already released.
 Cannot run queries anymore.
  • 技术栈版本:typeorm 0.2.33、nest 7.5.1

相关代码

业务代码

async testTransaction(user: User, dto: Dto): Promise<boolean> {
        const queryRunner = await getConnection().createQueryRunner();

        try {
            await queryRunner.startTransaction();
            const manager = queryRunner.manager;

            const aEntity = this.utils.createEntity(dto, user);
            const save1 = await manager.save(aEntity);

            const no1 = save1.no;
            const bEntity = await this.utils2.createEntity(dto, no1);
            const cEntity = await this.utils3.createEntity(dto, no1);

            await manager.save(bEntity);
            await manager.save(cEntity);
            await queryRunner.commitTransaction();
            return true;
        }
        catch (error) {
            await queryRunner.rollbackTransaction();
            throw new BadRequestException(error, "transaction failed");
        }
        finally {
            await queryRunner.release();
        }
}

独立测试代码

import { Injectable } from "@nestjs/common";
import { Connection } from "typeorm";
import { UserEntity } from "./entity/user.entity";
import { PGEntity } from "./entity/pg.entity";

@Injectable()
export class TransactionService {

    constructor( private connection: Connection ) { }

    async testTransaction() {
        const queryRunner = this.connection.createQueryRunner();

        await queryRunner.startTransaction();

        try {
            const user = queryRunner.manager.getRepository(UserEntity).create();
            user.name = 'transaction test';
            const userData = await queryRunner.manager.getRepository(UserEntity).save(user);

            const pg = queryRunner.manager.getRepository(PGEntity).create();
            pg.userNo = userData.no;
            pg.price = 20000;

            await queryRunner.manager.getRepository(PGEntity).save(pg);

            throw new Error('zzz')
            await queryRunner.commitTransaction();
        }

        catch (error) {
            console.error(error);
            await queryRunner.rollbackTransaction();
        }

        finally {
            await queryRunner.release();
        }
    }
}

独立测试日志

query: set session character_set_connection=utf8
query: set session character_set_client=utf8
query: set session character_set_results=utf8
query: set collation_connection=utf8_general_ci
query: set names utf8
query: set session character_set_connection=utf8
query: set session character_set_client=utf8
query: set session character_set_results=utf8
query: set collation_connection=utf8_general_ci
query: set names utf8
query: START TRANSACTION
query: set session character_set_connection=utf8
query: set session character_set_client=utf8
query: set session character_set_results=utf8
query: set collation_connection=utf8_general_ci
query: set names utf8
query: INSERT INTO `center`.`test_user`(`no`, `name`) VALUES (DEFAULT, ?) -- PARAMETERS: ["transaction test"]
query: set session character_set_connection=utf8
query: set session character_set_client=utf8
query: set session character_set_results=utf8
query: set collation_connection=utf8_general_ci
query: set names utf8
query: INSERT INTO `center`.`test_pg`(`no`, `user_no`, `price`) VALUES (DEFAULT, ?, ?) -- PARAMETERS: [5,20000]
Error: zzz
    at TransactionService.testTransaction (/Users/diehreo/Desktop/dev/iwedding_node_api/dist/src/transaction/transaction.service.js:32:19)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
    at async TransactionController.testTransaction (/Users/diehreo/Desktop/dev/iwedding_node_api/dist/src/transaction/transaction.controller.js:22:16)
query: ROLLBACK

修复方案

1. 修改数据库表引擎(核心解决事务不回滚问题)

MySQL的MyISAM引擎不支持事务,只有InnoDB引擎才支持事务回滚。执行以下SQL检查表引擎:

SHOW TABLE STATUS LIKE 'test_user'; -- 替换为你的目标表名

如果结果中Engine字段为MyISAM,执行修改语句切换为InnoDB:

ALTER TABLE test_user ENGINE=InnoDB; -- 替换为你的目标表名

将所有涉及事务操作的表都修改为InnoDB引擎。

2. 修复QueryRunner创建的异步问题

独立测试代码中,createQueryRunner()是异步方法,必须添加await,否则QueryRunner未初始化完成就启动事务,会导致事务绑定异常:

// 错误写法
const queryRunner = this.connection.createQueryRunner();
// 正确写法
const queryRunner = await this.connection.createQueryRunner();

3. 优化事务错误处理逻辑

在catch块中嵌套try-catch处理回滚和释放操作,避免回滚失败导致QueryRunner未正确释放:

catch (error) {
    console.error(error);
    // 单独捕获回滚错误,不影响后续释放流程
    try {
        await queryRunner.rollbackTransaction();
    } catch (rollbackErr) {
        console.error("事务回滚失败:", rollbackErr);
    }
} finally {
    // 单独捕获释放错误,避免异常扩散
    try {
        await queryRunner.release();
    } catch (releaseErr) {
        console.error("QueryRunner释放失败:", releaseErr);
    }
}

4. 提前校验实体有效性

在执行save前校验实体是否存在,避免传入undefined触发无效操作:

const cEntity = await this.utils3.createEntity(dto, no1);
if (!cEntity) {
    throw new BadRequestException("cEntity 不能为undefined");
}
await manager.save(cEntity);

修改后的完整代码示例

修改后的业务代码

async testTransaction(user: User, dto: Dto): Promise<boolean> {
    const queryRunner = await getConnection().createQueryRunner();

    try {
        await queryRunner.startTransaction();
        const manager = queryRunner.manager;

        const aEntity = this.utils.createEntity(dto, user);
        const save1 = await manager.save(aEntity);

        const no1 = save1.no;
        const bEntity = await this.utils2.createEntity(dto, no1);
        const cEntity = await this.utils3.createEntity(dto, no1);

        // 提前校验实体有效性
        if (!bEntity || !cEntity) {
            throw new BadRequestException("实体不能为undefined");
        }

        await manager.save(bEntity);
        await manager.save(cEntity);
        await queryRunner.commitTransaction();
        return true;
    } catch (error) {
        try {
            await queryRunner.rollbackTransaction();
        } catch (rollbackErr) {
            console.error("事务回滚失败:", rollbackErr);
        }
        throw new BadRequestException(error, "transaction failed");
    } finally {
        try {
            await queryRunner.release();
        } catch (releaseErr) {
            console.error("QueryRunner释放失败:", releaseErr);
        }
    }
}

修改后的独立测试代码

import { Injectable } from "@nestjs/common";
import { Connection } from "typeorm";
import { UserEntity } from "./entity/user.entity";
import { PGEntity } from "./entity/pg.entity";

@Injectable()
export class TransactionService {

    constructor(private connection: Connection) { }

    async testTransaction() {
        // 添加await获取QueryRunner
        const queryRunner = await this.connection.createQueryRunner();

        try {
            await queryRunner.startTransaction();

            const user = queryRunner.manager.getRepository(UserEntity).create();
            user.name = 'transaction test';
            const userData = await queryRunner.manager.getRepository(UserEntity).save(user);

            const pg = queryRunner.manager.getRepository(PGEntity).create();
            pg.userNo = userData.no;
            pg.price = 20000;

            await queryRunner.manager.getRepository(PGEntity).save(pg);

            throw new Error('zzz');
            await queryRunner.commitTransaction();
        } catch (error) {
            console.error(error);
            try {
                await queryRunner.rollbackTransaction();
            } catch (rollbackErr) {
                console.error("事务回滚失败:", rollbackErr);
            }
        } finally {
            try {
                await queryRunner.release();
            } catch (releaseErr) {
                console.error("QueryRunner释放失败:", releaseErr);
            }
        }
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.03 16:34:53