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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 17:42:02