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

如何在PostgreSQL Sequelize事务中获取真实数据库错误而非通用事务终止错误

解决Sequelize-Typescript事务中捕获真实数据库错误的问题

问题场景

在Sequelize-Typescript中使用事务创建Borrower记录及关联映射时,Borrower表部分字段设有唯一约束:

  • 不使用事务直接执行创建操作时,会返回明确的唯一约束冲突错误:original: error: duplicate key value violates unique constraint "borrowers_unique_cin",可直接反馈给前端用户。
  • 使用事务时,若请求包含重复字段值,会抛出通用事务终止错误:current transaction is aborted, commands ignored until end of transaction block,错误码为25P02,外层try-catch无法捕获原始的唯一约束冲突信息。

事务代码

return await this.sequelize.transaction(async (transaction) => {
  const borrower = await this.onBoardingServiceDao.createBorrower(
    body,
    transaction,
  );
  await this.onBoardingServiceDao.createUserBorrowerMapping(
    { userid: userId, borrowerid: borrower.id },
    transaction,
  );
  return borrower;
});

createBorrower方法实现

public async createBorrower(body, transaction: Transaction) {
  return await this.borrowerModel.create(body, { transaction });
}

事务错误堆栈示例

{
  "name": "SequelizeDatabaseError",
  "parent": {
    "length": 145,
    "name": "error",
    "severity": "ERROR",
    "code": "25P02",
    "detail": undefined,
    "hint": undefined,
    "position": undefined,
    "internalPosition": undefined,
    "internalQuery": undefined,
    "where": undefined,
    "schema": undefined,
    "table": undefined,
    "column": undefined,
    "dataType": undefined,
    "constraint": undefined,
    "file": "postgres.c",
    "line": "1498",
    "routine": "exec_parse_message",
    "sql": "INSERT INTO \"public\".\"borrowers\" (\"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\") VALUES (gen_random_uuid(),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING \"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\",\"onboarding_completed_at\",\"gdrive_folder_id\";",
    "parameters": [
      "ABC Software Technologies",
      "ABC",
      "CFE",
      "test@email.com",
      "9999999999",
      "",
      "",
      "",
      1,
      3,
      [
        "ADVISORY_SERVICES"
      ],
      2009
    ]
  },
  "original": {
    "length": 145,
    "name": "error",
    "severity": "ERROR",
    "code": "25P02",
    "detail": undefined,
    "hint": undefined,
    "position": undefined,
    "internalPosition": undefined,
    "internalQuery": undefined,
    "where": undefined,
    "schema": undefined,
    "table": undefined,
    "column": undefined,
    "dataType": undefined,
    "constraint": undefined,
    "file": "postgres.c",
    "line": "1498",
    "routine": "exec_parse_message",
    "sql": "INSERT INTO \"public\".\"borrowers\" (\"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\") VALUES (gen_random_uuid(),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING \"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\",\"onboarding_completed_at\",\"gdrive_folder_id\";",
    "parameters": [
      "ABC Software Technologies",
      "ABC",
      "CFE",
      "test@email.com",
      "9999999999",
      "",
      "",
      "",
      1,
      3,
      [
        "ADVISORY_SERVICES"
      ],
      2009
    ]
  },
  "sql": "INSERT INTO \"public\".\"borrowers\" (\"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\") VALUES (gen_random_uuid(),$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12) RETURNING \"id\",\"company_name\",\"company_pan\",\"company_cin\",\"contact_email\",\"contact_phone1\",\"contact_phone2\",\"company_website\",\"company_linkedin\",\"registration_type\",\"status\",\"operating_sectors\",\"incorporation_year\",\"onboarding_completed_at\",\"gdrive_folder_id\";",
  "parameters": [
    "ABC Software Technologies",
    "ABC",
    "CFE",
    "test@email.com",
    "9999999999",
    "",
    "",
    "",
    1,
    3,
    [
      "ADVISORY_SERVICES"
    ],
    2009
  ]
}

解决方案

1. 在事务回调内即时捕获原始错误

PostgreSQL中,事务内第一个操作抛出错误后,后续操作会触发25P02事务中止错误。因此必须在第一个出错的操作(createBorrower)后立即捕获错误,才能拿到原始的唯一约束冲突信息(错误码23505)。

修改后的事务代码:

return await this.sequelize.transaction(async (transaction) => {
  let borrower;
  try {
    // 直接在事务回调内捕获createBorrower的错误
    borrower = await this.onBoardingServiceDao.createBorrower(body, transaction);
  } catch (err) {
    // 判断是否为PostgreSQL唯一约束冲突错误(错误码23505)
    if (err.parent?.code === '23505') {
      // 提取约束名称,构造友好错误信息
      throw new Error(`字段值重复:违反唯一约束 ${err.parent.constraint}`);
    }
    // 其他错误正常抛出,事务自动回滚
    throw err;
  }
  // 若createBorrower成功,再执行关联映射创建
  await this.onBoardingServiceDao.createUserBorrowerMapping(
    { userid: userId, borrowerid: borrower.id },
    transaction,
  );
  return borrower;
});

2. 关键原理说明

Sequelize的自动事务(传入async回调的模式)会在回调抛出任何错误时自动执行回滚。但如果错误是在事务内后续操作触发的,此时事务已处于中止状态,只能拿到25P02的通用错误。只有在第一个出错的操作执行后立即捕获,才能获取到原始的23505错误及约束信息。

3. 可选:DAO层错误透传

确保DAO层不会吞掉原始错误,保持错误信息的完整性:

public async createBorrower(body, transaction: Transaction) {
  try {
    return await this.borrowerModel.create(body, { transaction });
  } catch (err) {
    // 直接透传错误,不做额外包装,保留原始错误的所有属性
    throw err;
  }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 14:54:53