3

给定一个像这样的简化表结构:

 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
    )
)

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

4

3 回答 3

8

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

客户提交您的上述查询。只要引擎找到MAX(num)只持有与其他读取器兼容的锁的同时,另一个客户端就可以在执行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
    
  2. 重试失败。我阅读了一篇关于每秒事务数非常高的系统的可信文章,该系统的场景与此不完全相同,但遭受了INSERT可能使用错误值的相同竞争条件。他们发现,通过首先给出num唯一约束,然后正常进行,可以实现最高 TPS,如果INSERT由于违反唯一约束而被拒绝,客户端将简单地重试。

  3. 添加一个锁定提示,强制引擎阻止其他读取器,直到INSERT完成。虽然这在技术上可能很容易,但它可能不适合高并发。如果MAX()使用单次搜索执行,并且阻塞时间不长并且不会阻塞许多客户端,理论上它是可以接受的,但大多数系统会随着时间的推移而增长,很快就会出现这种风险。

  4. 使用单独的单行辅助表记录num. 对辅助表执行同时读取和UPDATE,然后将读取的值分别用于INSERT主表。在我看来,这有点令人烦恼,因为它不是单个查询,而且它确实存在这样的问题,即如果客户端设法“保留” 的值num,但由于任何原因未能实际执行INSERT,则可能会出现间隙在表中的值num

于 2013-04-01T19:35:49.103 回答
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;

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

于 2013-04-01T20:48:23.603 回答
0
DROP SCHEMA tmp CASCADE;
CREATE SCHEMA tmp ;
SET search_path=tmp;

应该评估所有子查询,就好像它们是在查询开始时拍摄的快照一样。在 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)
于 2013-04-01T21:28:31.343 回答