2013-08-01 39 views
0

背景資料PostgreSQL的 - 腳本使用事務塊無法創建的所有記錄

我想了解PostgreSQL的不夠好,這樣我可以確保當用戶試圖同時我的web應用程序不會失敗更新相同的記錄。

出於測試目的,我已經創建了兩個腳本 - 一個腳本創建/定義事務塊和所述第二腳本模擬負載(和試圖創建衝突)通過調用腳本1.

代碼

下面是腳本1的樣子:

http://pastebin.com/6BKyx1dW

這裏就是腳本兩位則是:

http://pastebin.com/cHUhCBL2

爲了模擬數據庫的負載和測試鎖定問題,我從我的服務器上的兩個不同命令行窗口調用腳本2。我傳入兩組不同的參數,以便在分析數據庫中的結果時,我可以看到哪個會話創建了哪條記錄。

問題

當我查詢數據庫計數腳本的每個實例有多少記錄創建的,我沒有一直獲得各200。 我已經捕獲了腳本的每個實例的結果,以查看是否有任何回滾記錄,但沒有記錄。 所以我有兩個理論。

  1. 是正在執行這些腳本的服務器是不是足夠強大,因此該請求不能夠進入數據庫服務器...
  2. 數據庫服務器默默地中止交易。

爲了消除理論1,我將設置兩個不同的服務器,並從每臺服務器運行一次腳本,而不是在1臺服務器上打開2個命令行。 如果創建的記錄數量增加......我想這會告訴我,當前服務器的性能是一個問題。 (我目前正在運行腳本的「服務器」只是一個榮耀的桌面......所以它可能是問題)。

關於理論數字2,我一直在嘗試閱讀和理解http://www.postgresql.org/docs/current/static/explicit-locking.html 但由於我不是數據庫專家,因此花了我一些時間來消化所有內容。 我知道在MS SQL Server中,如果記錄被事務A鎖定,事務B將無限期地等待直到A完成。 使用SQLLite開箱即可,事務B死亡。但是您可以指定重試之前等待的毫秒數。

上面列出的postgresql文檔中的最後一段說postgresql也會無限期地等待衝突鎖被釋放......但我並不是100%肯定我不會在我的東西中搞砸sql代碼。

所以我的問題如下:

  1. 我做錯什麼明顯的錯誤在我的SQL代碼?
  2. 我該如何測試事物的sql方面,以查看哪些鎖正在使用/引擎蓋下發生了什麼?

編輯1

我從2周獨立的機器再次運行該腳本。機器1成功創建了122條記錄,機器2創建了183.

+0

你看過db日誌嗎?如果存在主鍵違規,他們將被記錄在那裏。 – bma

+0

是的......在數據庫日誌中沒有任何東西跳出來。 –

+0

我認爲這將有助於重申您的目標。 (非常簡要地)看看你的Lua,我看到你正在做一些事情。我記得幫助一位開發人員,在那裏獲得下一個可用插槽的解決方案竟然是使用序列(單調增加和原子級)和RETURNING子句將序列ID發回給調用者的組合。這消除了大部分的競爭條件。我認爲序列使用CYCLE命令重新啓動時,它打到200. – bma

回答

1

是的,你做錯了什麼。
看一個簡單的例子。

會話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個會話正在同時嘗試從該表中獲取下一行 - 每個會話都會掛起,並且將等待直到上一個會話將提交。
因此,不要在生產代碼中使用此查詢。
你真的想「查找並保留表中的下一個值」嗎?爲什麼?
如果是的話,你必須有一些序列化設備(如這個查詢,或者可能更容易實現,鎖定整個表),但這將是一個瓶頸。

+0

嗨!我想我知道我做錯了什麼。這就是我尋求幫助的原因。我不明白底層鎖定機制是如何工作的。你能告訴我什麼我可以嘗試改變我的代碼? –

+0

kordirko,非常感謝你的擴展解釋和樣本解決方案。我會嘗試應用你所建議的內容,看看會發生什麼 –