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

使用Promise.all致数据库操作非顺序执行,产生重复数据问题求助

解决Strapi中分类重复创建的竞态问题

问题根源

你遇到的重复记录完全是因为Promise.all并行执行所有异步操作导致的竞态条件:多个请求同时检查某分类不存在,然后同时执行创建操作,最终生成重复数据。


问题解答

1. 确保检查与插入顺序执行

把并行执行改成串行处理,或者先对分类名称去重再处理,就能避免同时检查的问题:

方案1:串行循环(最直接)

用for...of替代map + Promise.all,让每个分类的检查和创建操作在前一个完成后再执行:

for (const strain of manipulatedData) {
  const existsCategory = await strapi.service('api::category.category').find({
    filters: { name: strain.Category },
  });

  if (existsCategory.results.length === 0) {
    await strapi.service('api::category.category').create({
      data: { name: strain.Category },
    });
  }
}
方案2:先去重再处理(更高效)

如果manipulatedData里有大量重复的分类名称,先提取唯一值再处理,减少不必要的数据库查询:

// 提取所有不重复的分类名称
const uniqueCategoryNames = [...new Set(manipulatedData.map(item => item.Category))];

// 串行处理唯一分类
for (const name of uniqueCategoryNames) {
  const exists = await strapi.service('api::category.category').find({
    filters: { name },
  });

  if (!exists.results.length) {
    await strapi.service('api::category.category').create({ data: { name } });
  }
}

2. Strapi的高效处理方式

Strapi官方没有直接提供findOrCreate方法,但可以通过两种方式实现:

方式1:数据库唯一约束(最可靠)

在分类模型的schema.json中给name字段添加唯一约束,让数据库层面直接拦截重复数据:

// src/api/category/content-types/category/schema.json
{
  "attributes": {
    "name": {
      "type": "string",
      "required": true,
      "unique": true // 新增唯一约束
    }
  }
}

然后在创建时捕获唯一约束错误(不同数据库错误码不同,比如PostgreSQL是23505):

try {
  await strapi.service('api::category.category').create({
    data: { name: categoryName },
  });
} catch (err) {
  // 仅忽略唯一约束违反的错误,其他错误正常抛出
  if (err.code !== '23505') throw err;
}

这种方式即使代码有竞态,数据库也会直接阻止重复,是最安全成员Lou Ax逮运行表Hill �, 施加}

##### 方式2:自定义`findOrCreate`服务
在分类服务中封装一个通用方法,简化调用:
```javascript
// src/api/category/services/category.js
module.exports = ({ strapi }) => ({
  async findOrCreate(name) {
    const existing = await strapi.service('api::category.category').find({
      filters: { name },
    });
    if (existing.results.length) return existing.results[0];
    return await strapi.service('api::category.category').create({ data: { name } });
  },
});

调用时直接用这个方法:

await strapi.service('api::category.category').findOrCreate(strain.Category);

3. 事务或锁的必要性

  • 事务:主要用于需要原子性的多操作场景(比如创建分类同时关联其他数据),但单独用事务无法完全解决竞态问题——默认的读提交隔离级别下,多个事务还是能同时读到“分类不存在”的状态。
  • 锁:可以通过数据库原生查询加行锁,但实现复杂且影响性能,完全没必要——数据库唯一约束已经是最高效且可靠的防重复方案。

最优实践总结

  1. 给分类的name字段添加数据库唯一约束(兜底);
  2. 先对分类名称去重,再串行处理或并行调用findOrCreate并捕获错误;
  3. 避免用Promise.all直接处理未去重的创建逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 22:47:10