TypeScript中MySQL事务回滚失效问题求助
事务回滚失效的问题排查与修复
核心问题分析
你的代码里事务回滚不生效,根源是事务操作没有共用同一个数据库连接,导致插入操作不在事务范围内:
1. sqlQuery每次获取新连接,脱离事务上下文
sqlQuery函数每次调用都会通过pool.getConnection获取新的数据库连接,而事务要求所有相关操作必须在同一个连接上执行。这就导致插入job_posts的操作和事务的beginTransaction/commit/rollback用的不是同一个连接,插入的数据根本不受事务控制,自然不会回滚。
2. runTransaction未传递事务连接给业务逻辑
runTransaction函数获取到事务连接后,没有把这个连接传递给queryFunction,导致业务逻辑里的sqlQuery无法复用事务连接,只能重新拿新连接。
3. forEach + async导致异步操作未正确等待
在插入职位位置时,使用forEach结合异步函数,会导致所有插入操作并行执行且不会等待全部完成,可能在事务commit时部分插入还没完成,同时这种写法也会让错误无法被try/catch捕获(不过这不是回滚失效的直接原因)。
4. 连接释放的潜在错误
当pool.getConnection出错时,con是undefined,此时调用con.release()会抛出未定义错误,导致错误处理逻辑异常。
具体修复方案
步骤1:修改sqlQuery,支持复用已有连接
让sqlQuery可以接收外部传入的连接,事务场景下复用同一个连接,非事务场景下自动从连接池获取:
// 调整sqlQuery,可选接收已有的数据库连接 const sqlQuery = (queryString: string, params: any[] | null, con?: any): Promise<QueryResult> => { // 若传入连接则直接使用,否则从连接池获取 const connectionPromise = con ? Promise.resolve(con) : new Promise<any>((resolve, reject) => { pool.getConnection((err, connection) => { if (err) reject(err); else resolve(connection); }); }); return connectionPromise.then(connection => { return new Promise<QueryResult>((resolve, reject) => { connection.query(queryString, params, (err, results) => { // 仅当是自己从连接池获取的连接时才释放,事务连接交给事务逻辑处理释放 if (!con) connection.release(); if (err) reject(err); else resolve(results); }); }); }); };
步骤2:修复runTransaction,传递事务连接并优化异步处理
重新定义事务函数类型,将连接传递给业务逻辑,同时优化异步流程和错误处理:
// 调整TransactionFunction类型,接收事务连接参数 type TransactionFunction = (con: any) => Promise<void>; function runTransaction(queryFunction: TransactionFunction): Promise<void> { return new Promise<void>((resolve, reject) => { pool.getConnection((err, con) => { if (err) { // 拿连接出错时直接reject,避免调用undefined的release方法 reject(err); return; } con.beginTransaction((err) => { if (err) { con.release(); reject(err); return; } // 将事务连接传入业务逻辑函数 queryFunction(con) .then(() => { con.commit((err) => { if (err) { con.rollback(() => { con.release(); reject(err); }); } else { con.release(); resolve(); } }); }) .catch((error) => { con.rollback((err) => { if (err) { con.release(); reject(err); } else { con.release(); reject(error); } }); }); }); }); }); }
步骤3:修改API业务逻辑,使用事务连接并修复异步循环
在调用runTransaction时,使用传入的连接执行所有SQL操作,同时将forEach改为for...of确保异步操作顺序执行:
// 原API中的runTransaction调用部分修改为: await runTransaction(async (con) => { let insertResult = await sqlQuery(` INSERT INTO job_posts (post_title,post_content,category_id,permit_type_id) VALUES (?,?,?,?)`,[jobPostTile, jobPostContent, jobCategoryID, jobPermitTypeID], con); // 无需JSON.parse(JSON.stringify),直接获取insertId const numID: number = insertResult.insertId; // throw new Error(); // 测试事务回滚用 // 用for...of替代forEach,确保所有插入操作完成后再提交事务 for (const locationId of jobLocations) { await sqlQuery(` INSERT INTO job_posts_locations (job_post_id, location_id) VALUES (?,?)`,[numID, locationId], con); } });
额外优化点
- HTTP状态码规范:未授权请求返回
401,参数错误返回400,不要统一用500(500是服务器内部错误) - 简化响应发送:用
res.json('消息内容')替代JSON.stringify+res.send - 变量拼写修正:
jobPostTile改为jobPostTitle,避免拼写错误 - 语义化状态码:将
checkAuthentication返回的数字状态改为枚举常量,提升代码可读性
内容的提问来源于stack exchange,提问作者EQ0920
相关产品推荐
相关产品推荐

