2013-04-01 29 views
2

鑑於這樣一個簡化的表結構:如何保證原子SQL插入子查詢?

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,然後導致違反唯一約束。

+0

而在一個不相關的音符,沒有人知道爲什麼那麼拒絕任何問題與短語爲「CREATE TABLE」?只是給了我一個錯誤... – FtDRbwLXw6

+0

是的,這將導致競爭條件,你可以嘗試做的是使用序列通過使用seq.nextval生成數字,這將返回每個插入的唯一編號。 –

+0

有沒有理由不想在這裏使用序列? –

回答

5

是的,這當然可以創建競爭條件,因爲儘管所有語句都是原子保證的,但並不要求它們在查詢執行的不同部分期間在不變的數據集上進行操作。

客戶提交您的上述查詢。只要引擎發現MAX(num),同時只持有與其他讀取器兼容的鎖,則在執行INSERT之前,另一個客戶端可以找到相同的MAX(num)

有解決此問題的四種方法,我知道的:

  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的值可能會出現間隙。

+0

我很害怕這個。所以我需要一個表鎖來完成這個? – FtDRbwLXw6

+0

非常感謝您對此深入的回答。 #1和#4不適合我的需求,因爲它們都不能抵抗可能的差距。我在想#2可能是最好的選擇。 – FtDRbwLXw6

+2

差距有什麼危害?如果按照失敗重試進行操作,請絕對確定**您的'Max()'查詢執行查找並且未執行掃描。如果沒有這個地方,你將會有一個系統,隨着它的規模增長將會有一天失敗。另請參閱我的#3更新。 – ErikE

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; 

既導致性能問題做同樣的操作併發進程。但如果保證無間隙序列是需求,那麼這就是成本。 彷彿的是在查詢開始的快照

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

我沒有看到任何併發性在這裏檢查,如果這種模式沒有競爭條件「有效」。 – ErikE