如何保证子查询中的原子SQL插入?

FtD*_*Xw6 2 sql oracle race-condition

给定一个简化的表结构,如下所示:

 CREATE TABLE t1 (
        id INT,
        num INT,
        CONSTRAINT t1_pk
        PRIMARY KEY (id),
        CONSTRAINT t1_uk
        UNIQUE (id, num)
    )
Run Code Online (Sandbox Code Playgroud)

我可以使用这样的子查询来插入记录而不会导致竞争条件吗?

INSERT INTO t1 (
    id,
    num
) VALUES (
    1,
    (
        SELECT MAX(num) + 1
        FROM   t1
    )
)
Run Code Online (Sandbox Code Playgroud)

或者子查询不是原子的?我担心同时INSERT获取相同的值num然后导致唯一的约束违规.

Eri*_*ikE 5

是的,这肯定会创建竞争条件,因为虽然所有语句都是原子保证的,但这并不要求它们在查询执行的各个部分中跨越不变的数据集进行操作.

客户提交您的上述查询.只要引擎发现MAX(num)while只保留与其他阅读器兼容的锁,那么另一个客户端可以MAX(num)INSERT执行之前找到相同的锁.

我知道有四种解决这个问题的方法:

  1. 使用序列.INSERT你可以做sequencename.nextval回要插入的下一个唯一编号.

    SQL> create sequence t1num;
    
    Sequence created.
    
    SQL> select t1num.nextval from dual;
    
       NEXTVAL
    ----------
             1
    
    SQL> select t1num.nextval from dual;
    
       NEXTVAL
    ----------
             2
    
    Run Code Online (Sandbox Code Playgroud)
  2. 重试失败.我读了一篇关于非常高的每秒事务系统的可靠文章,该系统的场景与此不完全相同,但遭遇INSERT可能使用错误值的相同竞争条件.他们发现最高的TPS是通过简单地实现 - 给出num了一个独特的约束 - 如果INSERT由于违反了唯一约束而被拒绝,客户端就会重试.

  3. 添加一个锁定提示,强制引擎阻止其他读取器,直到INSERT完成.虽然这可能很容易,但它可能适用于高并发性,也可能不适用于高并发性.如果MAX()使用单个搜索执行,并且阻塞不长并且不阻止许多客户端,则可以完全接受.

  4. 使用单独的单行帮助程序表来记录下一个/最近的值num.UPDATE在辅助表上执行,同时拉出值,然后单独使用它INSERT到主表.在我看来,虽然这有一些不是单一查询的烦恼,但是,它确实存在这样的问题:如果客户端设法"保留"一个值num,但由于任何原因而无法实际执行INSERT,那么差距可以出现在num表中的值中.

  • 有差距有什么害处?如果你继续重试失败,那么绝对肯定你的`Max()`查询进行了搜索并且没有进行扫描.如果没有这个,你将拥有一个随着规模的增长而失败的系统.另外,请参阅我对#3的更新. (2认同)