You need to enable JavaScript to run this app. 多对多关系表使用原生SQL插入记录的正确方法是什么?
优惠活动
大模型
产品
解决方案
定价
更多

多对多关系表使用原生SQL插入记录的正确方法是什么?

多对多关联表插入的竞态问题与正确实现方案

现有写法的问题

你担心的竞态问题确实存在,现有写法有严重的安全隐患:

  • 哪怕在事务内部,只要数据库隔离级别没有设置为可串行化,MAX(id) 查询完全可以读取到其他并发事务已提交的新记录ID,最终会把不属于当前操作的 batch_id 和 orderline_id 插入到关联表,直接导致关联关系错误、数据损坏。
  • 就算将隔离级别调整为可串行化,也会大幅提升事务冲突概率,出现大量回滚,性能极差,完全不适合生产环境使用。

正确实现方案

核心逻辑是只获取当前会话/当前事务插入的记录主键,禁止全局查询最大ID,不同数据库的实现方式如下:

方案1:支持 RETURNING 子句的数据库(PostgreSQL、SQLite 3.35+、Oracle 12c+)

可以用 CTE 一次性完成三次插入,全程原子性,没有竞态风险:

WITH inserted_batch AS (
    INSERT INTO batches (name, sku, eta) 
    VALUES ('test1', 'BLUE CHAIR', null)
    RETURNING id
), inserted_orderline AS (
    INSERT INTO order_lines (order_id, amount, sku) 
    VALUES (157, 140, 'BLUE CHAIR')
    RETURNING id
)
INSERT INTO allocations (batch_id, orderline_id)
SELECT inserted_batch.id, inserted_orderline.id 
FROM inserted_batch, inserted_orderline;

方案2:MySQL 系列数据库

使用会话级别的 2770200 函数,它只会返回当前会话最近一次插入生成的自增ID,完全不受其他并发会话影响:

BEGIN;
    INSERT INTO batches (name, sku, eta) VALUES ('test1', 'BLUE CHAIR', null);
    SET @batch_id = 2770200;
    INSERT INTO order_lines (order_id, amount, sku) VALUES (157, 140, 'BLUE CHAIR');
    SET @orderline_id = 2770200;
    INSERT INTO allocations (batch_id, orderline_id) VALUES (@batch_id, @orderline_id);
COMMIT;

特殊场景:主键为业务自定义生成

如果你的表主键不是数据库自增,是业务逻辑提前生成的,那么直接使用预先生成的ID插入三张表即可,不存在主键获取的竞态问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.09 23:57:01