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

线上MySQL首次更新JSON字段时winnerCount字段未生效,本地环境无此问题

线上MySQL首次更新JSON字段时winnerCount字段未生效,本地环境无此问题

看起来你遇到了个挺诡异的环境差异问题——本地调试一切正常,一上线首次更新JSON字段里的winnerCount就不生效,第二次调用又恢复正常。我帮你梳理下可能的原因和对应的解决思路:

可能的原因分析

  1. 「读取-修改-写入」模式的潜在一致性问题
    你的代码先通过getCurrentFileUrl读取JSON字段,修改后调用updateWinner,而updateWinner里又重新查了一次getCurrentWinnerCount。虽然你说首次调用就出问题,并发概率低,但线上数据库可能存在缓存或事务隔离级别的差异,导致两次读取的数据不一致,最终写回的JSON不是你修改后的版本。

  2. JSON序列化/反序列化的环境差异
    本地和线上的Node.js、MySQL版本可能不一致:

    • 老版本Node.js对JSON.stringify处理新增属性的对象时,可能存在隐式忽略的情况(比如对象被意外冻结);
    • 老版本MySQL(比如5.7早期)对JSON字段的字符串赋值兼容性不如8.0,可能解析时丢弃了未定义过的键(这种情况虽然少见,但确实存在)。
  3. 直接替换整个JSON字段的风险
    你现在是把修改后的JSON序列化成字符串,直接赋值给fileUrl字段。如果序列化后的字符串在转义过程中(escapeSqlString)出现意外格式问题,MySQL可能会静默修正JSON格式,导致新增的winnerCount字段丢失。

针对性解决方案

方案1:用MySQL JSON函数直接修改指定字段,规避全量替换

这是最可靠的方式,跳过「读取-修改-写入」的流程,直接在数据库层面修改JSON里的目标值,完全规避序列化和并发问题。

修改updateWinner函数,用MySQL的JSON_SEARCH找到数组中对应fileUrlId的元素路径,再用JSON_SET更新winnerCount:

const updateWinner = async (id, fileUrlId) => {
  // 获取当前外层winnerCount(如果需要更新该字段)
  const resultGet = await getCurrentWinnerCount(id);
  if (resultGet.length !== 1) return null;

  const currentWinnerCount = resultGet[0].winnerCount ? resultGet[0].winnerCount + 1 : 1;
  
  // 使用MySQL JSON函数精准更新数组中指定元素的winnerCount
  const sql = `
    UPDATE vs_make_lists
    SET 
      fileUrl = JSON_SET(
        fileUrl,
        JSON_UNQUOTE(JSON_SEARCH(fileUrl, 'one', '${fileUrlId}', NULL, '$[*].id')),
        JSON_SET(
          JSON_EXTRACT(fileUrl, JSON_UNQUOTE(JSON_SEARCH(fileUrl, 'one', '${fileUrlId}', NULL, '$[*].id'))),
          '$.winnerCount',
          COALESCE(JSON_EXTRACT(fileUrl, CONCAT(JSON_UNQUOTE(JSON_SEARCH(fileUrl, 'one', '${fileUrlId}', NULL, '$[*].id')), '.winnerCount')), 0) + 1
        )
      ),
      winnerCount = '${currentWinnerCount}'
    WHERE list_id = ${id}
  `;
  return execSQL(sql);
};

注:JSON_SEARCH返回带引号的路径,需要用JSON_UNQUOTE转成实际路径;COALESCE用来处理winnerCount不存在的情况,默认从0开始累加。

方案2:增加日志排查序列化环节问题

如果暂时不想改数据库操作逻辑,可以先在代码里加日志,定位问题出在哪一步:

// 在router的reduce修改后添加日志
console.log("修改后的tempFileUrl:", tempFileUrl);
console.log("序列化后的JSON字符串:", JSON.stringify(tempFileUrl));

// 在updateWinner里添加SQL执行日志
console.log("执行的UPDATE语句:", sql);

查看线上日志:

  • 如果序列化后的字符串里没有winnerCount,说明是reduce修改逻辑的问题(比如线上环境中item.id === fileUrlId的判断不成立,可以加日志打印item.id和fileUrlId的类型与值);
  • 如果序列化后的字符串有winnerCount但数据库里没有,说明是MySQL的问题,检查线上MySQL版本、确认fileUrl字段是JSON类型而非VARCHAR,也可以用JSON_VALID函数验证序列化后的字符串是否为合法JSON。

方案3:优化现有逻辑,减少重复查询

把updateWinner里的getCurrentWinnerCount查询去掉,直接用router里已经读取到的数据,避免两次查询的不一致:

// 修改router逻辑,传递已获取的winnerCount
router.get("/winner", async function (req, res, next) {
  let id = Number(req.query.id);
  let fileUrlId = req.query.fileUrlId;

  if (!id && !fileUrlId) {
    return res.send(failModel(2004, false, null));
  }

  const resGetWC = await getCurrentFileUrl(id);
  if (resGetWC.length === 1) {
    const currentWinnerCount = resGetWC[0].winnerCount ? resGetWC[0].winnerCount + 1 : 1;
    const tempFileUrl = JSON.parse(resGetWC[0].fileUrl).reduce((result, item) => {
      if (item.id === fileUrlId) {
        item.winnerCount = item.winnerCount ? item.winnerCount + 1 : 1;
      }
      result.push(item);
      return result;
    }, []);

    const resultUpdate = await updateWinner(id, JSON.stringify(tempFileUrl), currentWinnerCount);
    if (resultUpdate?.affectedRows === 1) {
      return res.send(successModel());
    }
    return res.send(failModel(-1, false, null));
  }
  return res.send(failModel(-1, false, null));
});

// 修改updateWinner函数
const updateWinner = async (id, fileUrl, currentWinnerCount) => {
  const sql = `
    UPDATE vs_make_lists
    SET fileUrl = ${escapeSqlString(fileUrl)}, winnerCount ='${currentWinnerCount}'
    WHERE list_id = ${id}
  `;
  return execSQL(sql);
};

备注:内容来源于stack exchange,提问作者Tokyo

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.22 09:34:35