Node Express应用高并发下‘不存在则插入’逻辑失效问题
解决高并发下Oracle数据库重复插入问题
你的代码逻辑是先查询再插入,这两步并非原子操作。在每秒100次请求的高并发场景下,多个请求会同时执行查询,都得到“无对应记录”的结果,随后各自触发插入操作,最终导致多条重复数据被写入数据库。
以下是几种可靠的解决方案:
方案1:数据库唯一约束+异常捕获(最基础可靠)
从数据库层面阻止重复数据生成,同时在应用层处理冲突异常:
- 给目标表添加唯一约束,约束字段对应你用来判断重复的业务字段(比如假设是
column1和column2,根据实际业务调整):
ALTER TABLE your_table_name ADD CONSTRAINT unique_search_record UNIQUE (column1, column2);
- 修改接口代码,捕获插入时的唯一约束冲突异常:
app.post('/v1/test',async function(req, res) { const searchInitialStartTime = new Date(); console.log(req.requestUID); try{ let dbResult = await dao.getSerachDate(req.body); console.log(req.requestUID,dbResult.rows.length); if(dbResult.rows.length == 0){ try { await dao.insertSerachDate(req.body); // 插入成功后重新查询最新数据返回 dbResult = await dao.getSerachDate(req.body); } catch (insertErr) { // 捕获Oracle唯一约束冲突错误(错误码通常为ORA-00001) if (insertErr.message.includes('ORA-00001')) { console.log(req.requestUID, "记录已存在,无需重复插入"); // 冲突时重新查询已有数据返回 dbResult = await dao.getSerachDate(req.body); } else { throw insertErr; } } } await utils.logSummeryDetailsInDB(null, "test", "200"); return res.json(dbResult.rows); } catch(e){ console.log(e); res.status(400).send("Error"); } });
即使多个请求同时触发插入,数据库会通过唯一约束拦截重复操作,应用捕获异常后直接返回已存在的数据即可。
方案2:使用Oracle MERGE语句(原子操作,高效)
MERGE语句能在单条SQL中完成“查询-插入”的原子操作,彻底避免并发问题:
- 在DAO层新增MERGE方法:
// dao.js中的merge方法示例 async function mergeSerachDate(body) { const sql = ` MERGE INTO your_table_name t USING (SELECT :column1 AS column1, :column2 AS column2 FROM DUAL) s ON (t.column1 = s.column1 AND t.column2 = s.column2) WHEN NOT MATCHED THEN INSERT (column1, column2, other_column) VALUES (s.column1, s.column2, :other_value) `; const binds = { column1: body.column1, column2: body.column2, other_value: body.other_value }; await execute(sql, binds); }
- 修改接口代码,直接调用MERGE操作:
app.post('/v1/test',async function(req, res) { const searchInitialStartTime = new Date(); console.log(req.requestUID); try{ // 执行原子MERGE操作,不存在则插入,存在则不做操作 await dao.mergeSerachDate(req.body); // 查询最新数据返回 const dbResult = await dao.getSerachDate(req.body); await utils.logSummeryDetailsInDB(null, "test", "200"); return res.json(dbResult.rows); } catch(e){ console.log(e); res.status(400).send("Error"); } });
MERGE由数据库保证原子性,无论多少并发请求,只会插入一条目标记录。
方案3:应用层分布式锁(可选,复杂度高)
如果无法修改数据库或使用MERGE,可以尝试用Redis等实现分布式锁:
- 用业务关键字段生成唯一锁键,请求先获取锁
- 拿到锁后再执行查询-插入逻辑
- 操作完成后释放锁
但这种方案存在锁超时、死锁等风险,可靠性不如数据库层面的方案,仅作为备选。
内容的提问来源于stack exchange,提问作者Chinmoy Samanta
相关产品推荐
相关产品推荐

