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
相关产品推荐
相关产品推荐

