使用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. 事务或锁的必要性
- 事务:主要用于需要原子性的多操作场景(比如创建分类同时关联其他数据),但单独用事务无法完全解决竞态问题——默认的读提交隔离级别下,多个事务还是能同时读到“分类不存在”的状态。
- 锁:可以通过数据库原生查询加行锁,但实现复杂且影响性能,完全没必要——数据库唯一约束已经是最高效且可靠的防重复方案。
最优实践总结
- 给分类的
name字段添加数据库唯一约束(兜底); - 先对分类名称去重,再串行处理或并行调用
findOrCreate并捕获错误; - 避免用
Promise.all直接处理未去重的创建逻辑。
内容的提问来源于stack exchange,提问作者DanialDP
相关产品推荐
相关产品推荐

