NestJs+TypeOrm+Mysql嵌套使用subscriber导致应用崩溃问题排查
问题触发原因
- 订阅者重复注册:你在ORM配置中已经开启了subscribers自动扫描加载,同时又在每个订阅者的构造函数中手动将当前实例push到连接的订阅者列表中,导致每个订阅者被注册2次,事件触发2次。如果后续的Transaction实体插入又触发了前面实体的写入逻辑,会直接形成无限循环插入,快速占满数据库连接池,导致后续请求阻塞卡死。
- 事务嵌套阻塞:TypeORM的
afterInsert事件是在当前写入事务提交前触发的,外层save方法需要等待afterInsert的异步逻辑完全执行完成才会提交事务。而你在订阅者中使用注入的Repository执行写入操作时,会申请新的数据库连接开启独立事务,多层嵌套后所有外层事务都在等待内层逻辑完成再提交,再加上连接池资源被占,就会形成死锁式阻塞。从你的MySQL日志也能看出三个写入操作都执行了INSERT,但没有一个事务提交,就是因为外层事务一直在等待内层逻辑完成。 - 上下文管理器使用错误:订阅者事件回调中没有复用当前事件上下文的
entityManager,每次写入都申请新连接,进一步加剧连接池资源消耗。
解决方案
1. 修复订阅者重复注册问题
二选一即可,推荐使用Nest依赖注入管理方式:
- 删掉ORM配置中的
subscribers: ['dist/**/subscribers/*.subscriber{.ts,.js}']自动扫描配置 - 删掉每个订阅者构造函数中的
connection.subscribers.push(this)手动注册代码 - 在对应业务模块的
providers数组中加入所有订阅者,交给Nest统一实例化管理
2. 复用事件上下文的EntityManager
在订阅者的事件回调中,使用事件参数自带的entityManager执行所有数据库操作,复用当前事务的连接,不需要额外申请新连接,也避免嵌套事务问题,示例:
@Injectable() export class BankActionsSubscriber implements EntitySubscriberInterface<BankAction> { constructor(connection: Connection) {} listenTo = () => BankAction; async afterInsert(event: InsertEvent<BankAction>) { // 直接用event.manager操作,复用当前事务连接 const newOrder = event.manager.create(Order, example_data); await event.manager.save(newOrder); } }
3. 非核心逻辑异步解耦(可选)
如果后续的Order、Transaction写入不需要和BankAction写入保证原子性,可以用事件驱动异步解耦,避免阻塞主事务:
// 订阅者中直接发事件,不等待执行完成 async afterInsert(event: InsertEvent<BankAction>) { this.eventEmitter.emit('bank.action.created', event.entity); } // 单独的事件监听者执行后续写入 @OnEvent('bank.action.created') async handleBankActionCreated(data: BankAction) { await this.ordersService.create(example_data); }
内容的提问来源于stack exchange,提问作者Mahdi Amiri
相关产品推荐
相关产品推荐

