是的,你做錯了什麼。
看一個簡單的例子。
會話1
postgres=# select * from user_reservation_table;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | f | 0 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
postgres=# \set user 1
postgres=#
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=#
會話2 - 在同一時間,但只有10毫秒後
postgres=# \set user 2
postgres=# begin;
BEGIN
postgres=# UPDATE user_reservation_table
postgres-# SET UsedYesNo = true, userid=:user
postgres-# WHERE uservalue IN(
postgres(# SELECT uservalue FROM user_reservation_table
postgres(# WHERE UsedYesNo=false Order By id ASC Limit 1)
postgres-# RETURNING uservalue;
會話2個掛起.......和是等待東西....
回第1場
postgres=# commit;
COMMIT
postgres=#
又一次會議2
uservalue
-----------
1
(1 wiersz)
UPDATE 1
postgres=# commit;
COMMIT
postgres=#
會話2不候了,並完成它的交易。
什麼是最後的結局:
postgres=# select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
兩個用戶採取了同樣的值1,但只有用戶2表中登記
====== ================編輯================================= =
在這種情況下,我們可以使用SELECT .. FOR UPDATE並利用whi CH postgre重新評估在讀提交的隔離級別模式查詢,
看到文檔:http://www.postgresql.org/docs/9.2/static/transaction-iso.html
UPDATE, DELETE, SELECT FOR UPDATE, and SELECT FOR SHARE commands behave the same as SELECT in terms of searching for target rows: they will only find target rows that were committed as of the command start time. However, such a target row might have already been updated (or deleted or locked) by another concurrent transaction by the time it is found. In this case, the would-be updater will wait for the first updating transaction to commit or roll back (if it is still in progress). If the first updater rolls back, then its effects are negated and the second updater can proceed with updating the originally found row. If the first updater commits, the second updater will ignore the row if the first updater deleted it, otherwise it will attempt to apply its operation to the updated version of the row. The search condition of the command (the WHERE clause) is re-evaluated to see if the updated version of the row still matches the search condition. If so, the second updater proceeds with its operation using the updated version of the row. In the case of SELECT FOR UPDATE and SELECT FOR SHARE, this means it is the updated version of the row that is locked and returned to the client.
簡而言之:
如果一個會話鎖定了這一行,其他會話試圖鎖定同一行,然後第二個會話將「掛起」,並等待第一個會話提交或回滾。 當第一個會話提交事務時,第二個會話將重新評估WHERE搜索條件。 如果搜索條件不匹配(因爲第一個transacion更改了某些列),那麼第二個會話將跳過該行, 並將處理與WHERE條件匹配的下一行。
注意:此行爲在可重複讀取隔離級別中有所不同。 在這種情況下,第二次會話將拋出錯誤:由於併發更新而無法序列化訪問,並且您必須重試整個事務。
我們的查詢可能看起來像:
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update ;
然後:
Update .... where id = (id returned by SELECT ... FOR UPDATE)
就個人而言,我喜歡測試使用普通的,老控制檯程序(psql獲取postgree鎖定scenarious,MySQL的或SQLPlus for oracle)
因此,讓我們在psql中測試我們的查詢:
session1 #select * from user_reservation_table order by id;
id | usedyesno | userid | uservalue
----+-----------+--------+-----------
1 | t | 2 | 1
2 | f | 0 | 2
3 | f | 0 | 3
4 | f | 0 | 4
5 | f | 0 | 5
(5 wierszy)
session1 #begin;
BEGIN
session1 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
id
----
2
(1 wiersz)
session1 #update user_reservation_table set usedyesno = true
postgres-# where id = 2;
UPDATE 1
session1 #
會話1鎖定和更新的行ID = 2
現在工作階段
session2 #begin;
BEGIN
session2 #select id from user_reservation_table
postgres-# where usedyesno = false
postgres-# order by id
postgres-# limit 1
postgres-# for update ;
會議2個掛起,同時試圖鎖定該行ID = 2
OK,讓我們提交會議1
session1 #commit;
COMMIT
session1 #
看看會話2中會發生什麼:
postgres-# for update ;
id
----
3
(1 wiersz)
賓果 - 會議2跳過行ID = 2,選擇(並鎖定)行ID = 3
因此,我們的最終查詢可能是:
update user_reservation_table
set usedyesno = true
where id = (
select id from user_reservation_table
where usedyesno = false
order by id
limit 1
for update
) RETURNING uservalue;
一些預訂 - 這個例子僅僅是爲了你的測試目的,它的目的是幫助理解鎖定在postgre中的工作方式。
實際上,這個查詢將序列化對錶的訪問,並且不可伸縮,並且可能在多用戶環境中執行不良(緩慢)。
想象一下,10個會話正在同時嘗試從該表中獲取下一行 - 每個會話都會掛起,並且將等待直到上一個會話將提交。
因此,不要在生產代碼中使用此查詢。
你真的想「查找並保留表中的下一個值」嗎?爲什麼?
如果是的話,你必須有一些序列化設備(如這個查詢,或者可能更容易實現,鎖定整個表),但這將是一個瓶頸。
你看過db日誌嗎?如果存在主鍵違規,他們將被記錄在那裏。 – bma
是的......在數據庫日誌中沒有任何東西跳出來。 –
我認爲這將有助於重申您的目標。 (非常簡要地)看看你的Lua,我看到你正在做一些事情。我記得幫助一位開發人員,在那裏獲得下一個可用插槽的解決方案竟然是使用序列(單調增加和原子級)和RETURNING子句將序列ID發回給調用者的組合。這消除了大部分的競爭條件。我認爲序列使用CYCLE命令重新啓動時,它打到200. – bma