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

时间:2013-04-01 19:28:09

标签: sql oracle race-condition

给出一个简化的表结构:

 CREATE TABLE t1 (
        id INT,
        num INT,
        CONSTRAINT t1_pk
        PRIMARY KEY (id),
        CONSTRAINT t1_uk
        UNIQUE (id, num)
    )

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

INSERT INTO t1 (
    id,
    num
) VALUES (
    1,
    (
        SELECT MAX(num) + 1
        FROM   t1
    )
)

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

3 个答案:

答案 0 :(得分:5)

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

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

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

  1. 使用sequenceINSERT中您可以sequencename.nextval返回要插入的下一个唯一号码。

    SQL> create sequence t1num;
    
    Sequence created.
    
    SQL> select t1num.nextval from dual;
    
       NEXTVAL
    ----------
             1
    
    SQL> select t1num.nextval from dual;
    
       NEXTVAL
    ----------
             2
    
  2. 重试失败。我读了一篇关于非常高的每秒交易系统的可靠文章,该系统的情况与此不完全一样,但遭受了相同的竞争条件INSERT可能使用了错误的值。他们发现最高的TPS是通过简单地实现 - 给予num唯一约束 - 如果INSERT由于违反唯一约束而被拒绝,则客户端只需重试。

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

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

答案 1 :(得分:0)

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE;
INSERT INTO t1 (id, num) VALUES (1, (SELECT MAX(num) + 1 FROM t1));
COMMIT;

LOCK TABLE t1 IN EXCLUSIVE MODE;
INSERT INTO t1 (id, num) VALUES (1, (SELECT MAX(num) + 1 FROM t1));
COMMIT;

同时导致同时进程执行相同操作的性能问题。但如果要求保证无间隙序列是必需的,那么这就是成本。

答案 2 :(得分:0)

DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;

Al子查询应该进行评估,好像是在query-start时拍摄的快照。在Postgres中没有额外措施的工作:

CREATE TABLE hopla
        ( the_id SERIAL NOT NULL PRIMARY KEY
        , tralala varchar
        );

INSERT INTO hopla(tralala)
SELECT 'tralala_' || gs::text
FROM generate_series(1,4) gs
        ;

SELECT * FROM hopla;
INSERT INTO hopla(the_id, tralala)
SELECT mx.mx + row_number() OVER (ORDER BY org.the_id)
        , org.tralala
FROM hopla org
, (SELECT MAX(the_id) AS mx FROM hopla) mx
        ;

SELECT * FROM hopla;

结果/输出:

CREATE TABLE
INSERT 0 4
 the_id |  tralala  
--------+-----------
      1 | tralala_1
      2 | tralala_2
      3 | tralala_3
      4 | tralala_4
(4 rows)

INSERT 0 4
 the_id |  tralala  
--------+-----------
      1 | tralala_1
      2 | tralala_2
      3 | tralala_3
      4 | tralala_4
      5 | tralala_1
      6 | tralala_2
      7 | tralala_3
      8 | tralala_4
(8 rows)