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

Node Express应用高并发下‘不存在则插入’逻辑失效问题

解决高并发下Oracle数据库重复插入问题

你的代码逻辑是先查询再插入,这两步并非原子操作。在每秒100次请求的高并发场景下,多个请求会同时执行查询,都得到“无对应记录”的结果,随后各自触发插入操作,最终导致多条重复数据被写入数据库。

以下是几种可靠的解决方案:

方案1:数据库唯一约束+异常捕获(最基础可靠)

从数据库层面阻止重复数据生成,同时在应用层处理冲突异常:

  1. 给目标表添加唯一约束,约束字段对应你用来判断重复的业务字段(比如假设是column1和column2,根据实际业务调整):
ALTER TABLE your_table_name ADD CONSTRAINT unique_search_record UNIQUE (column1, column2);
  1. 修改接口代码,捕获插入时的唯一约束冲突异常:
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中完成“查询-插入”的原子操作,彻底避免并发问题:

  1. 在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);
}
  1. 修改接口代码,直接调用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 02:52:24