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

Nodejs+Sequelize(Mysql)跨请求读写异步操作竞态问题求解

Node.js Sequelize MySQL跨请求读写竞态解决方案

你提到的跨请求场景下普通事务无效的判断是正确的:不同请求的事务相互独立,没有加锁的普通事务仅能保证单请求内的操作原子性,无法阻挡多个请求同时读到空记录后重复写入的竞态问题。

以下是不同场景下的可行解决方案:

  • 方案1:数据库唯一约束(优先推荐)
    该方案是性能最高、逻辑最简洁、可靠性最强的解法,完全避免竞态的核心是利用数据库全局生效的原子性唯一校验规则,不受请求上下文限制。
    操作步骤:

    1. 匹配你的查询条件,给conversations表加联合唯一索引:如果你查询时的createdAt LIKE :time是按天匹配,可新增冗余字段created_date存储DATE类型的日期,索引字段为context、phoneNumber、created_date,建索引SQL:
      ALTER TABLE conversations ADD UNIQUE KEY uniq_ctx_phone_created (context, phoneNumber, created_date);
    2. 代码逻辑改为先尝试插入,捕获唯一约束异常来判断是否已存在,示例代码:
    try {
      await conversationsModel.create({
        phoneNumber: to.phoneNumber,
        context: "team",
        displayName: "xxx",
        externalId,
        message,
        type: "media",
        created_date: new Date().toISOString().slice(0,10) // 匹配你的时间过滤规则调整
      });
      return 'X';
    } catch (err) {
      // MySQL唯一约束冲突错误码为ER_DUP_ENTRY
      if (err.original?.code === 'ER_DUP_ENTRY' || err.name === 'SequelizeUniqueConstraintError') {
        return 'Y';
      }
      throw err;
    }
    
  • 方案2:带排他锁的事务(适合无法加唯一索引的场景)
    给查询操作加FOR UPDATE排他锁,事务开启后第一个请求的查询会锁定对应范围的行/间隙,后续同条件的查询会阻塞到第一个事务提交/回滚后再执行,从数据库层面避免幻读。
    示例代码:

    const transaction = await sequelize.transaction({
      isolationLevel: Sequelize.Transaction.ISOLATION_LEVELS.READ_COMMITTED
    });
    try {
      const existRecords = await sequelize.query(
        'SELECT xxx FROM conversations where context="yyy" AND phoneNumber=:phoneNumber AND createdAt LIKE :time FOR UPDATE',
        {
          replacements: { phoneNumber: to.phoneNumber, time: yourTimeParameter },
          type: Sequelize.QueryTypes.SELECT,
          transaction
        }
      );
      if (existRecords.length) {
        await transaction.commit();
        return 'Y';
      }
      await conversationsModel.create({
        phoneNumber: to.phoneNumber,
        context: "team",
        displayName: "xxx",
        externalId,
        message,
        type: "media"
      }, { transaction });
      await transaction.commit();
      return 'X';
    } catch (err) {
      await transaction.rollback();
      throw err;
    }
    

    注意事项:该方案需要控制事务执行时长,避免长事务导致大量请求阻塞。

  • 方案3:分布式锁(适合多实例部署的附加防护)
    可以基于Redis实现分布式锁,锁的key由查询条件拼接而成(如conversation:team:${to.phoneNumber}:${yourTimeParameter}),只有拿到锁的请求才可以执行查+写逻辑,执行完成后释放锁。该方案容错性低于数据库唯一约束,建议作为前两种方案的补充使用。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 08:30:00