2017-10-05 173 views
2

裏使用Postgres 9.6,我遵循了https://stackoverflow.com/a/40325406/435563建議做一個INSERTSELECT並返回得到的ID策略:INSERT或SELECT策略總是返回一行?

with ins as (
    insert into prop (prop_type, norm, hash, symbols) 
    values (
    $1, $2, $3, $4 
) on conflict (hash) do 
    update set prop_type = 'jargon' where false 
    returning id) 
select id from ins 
union all 
select id from prop where hash = $3 

不過,有時這種沒有返回。無論如何,我都會預料它會連續返回。我如何解決它以確保它始終返回一個ID?

注意,儘管沒有返回一行,但行似乎在檢查中存在。我相信這個問題可能與嘗試通過兩個會話同時添加相同的記錄有關。

問題的表被定義爲:

create table prop (
    id serial primary key, 
    prop_type text not null references prop_type(name), 
    norm text not null, 
    hash text not null unique, 
    symbols jsonb 
); 

數據:

EDT DETAIL: parameters: $1 = 'jargon', $2 = 'j2', $3 = 'lXWkZSmoSE0mZ+n4xpWB', $4 = '[]' 

如果我改變prop_type = 'jargon'prop_type = 'foo'它的作品!即使給出where false子句,如果表達式不會改變任何東西,看起來鎖也不會被採用。這真的需要依賴於我猜測一個不會排在行內的值嗎?還是有更好的方法來確保你獲得鎖定?

--- UPDATE ---

的總體情況是,應用程序嘗試使用連接池(...自動提交)保存向無環圖,並使用該查詢來獲取同時識別重複。 [事實證明,更聰明的是使用一個事務,只是序列化到一個連接。但是,當有爭這裏的行爲是奇數]

外鍵約束似乎並沒有影響到插入 - 如:

create table foo(i int unique, prop_id int references prop(id)); 
insert into foo values (1, 208); 
insert into foo values (1, 208) 
on conflict (i) do update set prop_id = 208 where false; 
--> INSERT 0 0 
insert into foo values (1, 208) 
on conflict (i) do update set prop_id = -208 where false; 
--> INSERT 0 0 

注意一個與有效FK 208,其他無效-208。如果我使用完整模式將選擇連接到其中任何一個上,那麼在沒有爭用的情況下,它們都會按預期返回i = 1。

+0

請發佈的數據樣本爲好,如果我不是弄錯它始終如果它返回一行,因此你甚至可以從XMIN告訴,XMAX已更新或插入 –

+0

已添加數據。我想它與應用程序試圖保存一個複雜的數據結構,其中該葉片出現兩次,使用連接池相關。所以記錄被添加到兩個不同的連接中。 – shaunc

+0

與您的更新我注意到哪裏更新不會發生錯誤 - 與該答案將不同:) –

回答

2

你的觀察似乎是不可能的。上述命令應該是總是返回一個id,無論是新插入的行還是預存在的行。併發寫入不能混淆這一點,因爲現有的衝突行被鎖定。說明在這個相關的答案:

除非將引發異常,當然。在這種情況下,您會收到錯誤消息而不是結果。你檢查了嗎?你有錯誤處理嗎? (如果你的應用程序以某種方式丟棄錯誤消息:1)解決這個問題。 2)有一個在數據庫日誌的默認日誌記錄設置的其他項)

我看到你的表定義一個FK約束:

prop_type text not null references prop_type(name), 

如果您嘗試插入一行違反約束條件,這正是發生的情況。如果在表prop_typename = 'jargon'排不出,這就是你會得到什麼:

ERROR: insert or update on table "prop" violates foreign key constraint "prop_prop_type_fkey" 
DETAIL: Key (prop_type)=(jargon) is not present in table "prop_type". 

演示:

dbfiddle here

你的觀察將適合的罪行:

如果我將prop_type ='術語'更改爲prop_type ='foo',它就可以工作!

但是你的解釋是基於誤解:

這似乎鎖如果表達式不會改變任何事情不採取甚至給出了其中假子句。

這不是Postgres的工作原理。鎖定是以任何一種方式進行的(上面的鏈接答案中的解釋),Postgres鎖定機制甚至不會考慮新的行與舊的行相比如何。

這是否真的需要依賴於我猜測不會在行中的值?還是有更好的方法來確保你獲得鎖定?

不,

如果缺少FK值確實是問題,那麼您可以在包含rCTE的單個語句中添加缺失(不同)值。簡單的單行插入就像你演示的一樣,但也適用於一次插入多行。相關閱讀:

+0

感謝您的回覆。有了你的確認,它似乎不太可能。事實上,'行話'在'prop_type'中,而'foo'不在。與此同時,我同時使用單個客戶端對整個對象圖進行序列化,而不是依賴於一個池,這意味着錯誤不會再現,但會嘗試查看我是否可以返回,因爲它看起來很奇怪對我來說。 – shaunc

+0

@shaunc:你的意思是相反的? 'prop_type'中的'foo'和* not *'術語?因爲如果'foo'不是*,這會開始變得非常奇怪...... –

+0

這就是我的意思(!)但是,現在我不能檢查你提到的其他可能性 - 那裏是我錯過了某種錯誤(儘管它們在應用程序中被拋出,除非被捕獲,並且我的測試應用程序日誌沒有任何信息;但我現在無法在pg_log中確定地找到它)。所以我傾向於這個選擇,因爲我知道它很難找到真正的錯誤。正如我所說,我不能用現行代碼重現,但它確實很奇怪,我確實想回去嘗試。如果*沒有錯誤,我應該尋找什麼樣的證據來證實發生了什麼奇怪的事情? – shaunc

1

https://www.postgresql.org/docs/9.5/static/sql-insert.html

ON CONFLICT DO UPDATE保證原子INSERT或UPDATE結果; 假設沒有獨立錯誤,即使在高併發的情況下,這兩個結果中的一個也是 。

這是關於您更新的帖子中的鎖定。現在關於最後一個問題 - 我首先仔細閱讀。現在我看到了where false - 這個子句並不總是返回一行。例如:

t=# create table a(i int, e int); 
CREATE TABLE 
t=# insert into a select 1,1; 
INSERT 0 1 
t=# create unique index b on a (i); 
CREATE INDEX 
---now insert on conflict do nothing: 
t=# insert into a select 1,1 on conflict do nothing returning *,xmax,xmin; 
i | e | xmax | xmin 
---+---+------+------ 
(0 rows) 

INSERT 0 0 
-- where false same effect - no rows 
t=# insert into a select 1,1 on conflict(i) do update set e=2 where false returning *,xmax,xmin; 
i | e | xmax | xmin 
---+---+------+------ 
(0 rows) 
-- now insert without conflict: 
t=# insert into a select 2,2 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax; 
i | e | xmax 
---+---+------ 
2 | 2 | 0 
(1 row) 
-- now insert with update on conflict: 
INSERT 0 1 
t=# insert into a select 1,1 on conflict(i) do update set e=2 where EXCLUDED.e=1 returning *,xmax; 
i | e | xmax 
---+---+----------- 
1 | 2 | 126943767 
(1 row) 
+0

這個問題的介紹是誤導性的,它實際上是關於'INSERT'或'SELECT',而不是'INSERT'或'UPDATE'。你可能錯過了附加的'UNION ALL SELECT ...'在問題中。 –

+0

@ErwinBrandstetter謝謝 - 確實我回答了錯誤的問題:) –