1

我有一個主鍵簡單的表。大部分讀取操作都是通過密鑰的確切值獲取一行。如何鎖定數據庫表或一系列行來寫入?

每行中的數據與鍵之前和之後的行保持一些關係。所以當我插入一個新行時,我需要讀取它要輸入的兩行,進行一些計算然後插入。

顯而易見的問題是,同時另一個連接可能會在相同的時間間隔內添加一個帶有鍵值的行。如果它與第二次插入失敗的密鑰值完全相同,我將予以報道,但如果密鑰值不同,但在相同的時間間隔內可能會破壞關係。

解決方案似乎是在我決定添加新行或(如果可能的話,我懷疑)鎖定鍵值的間隔時鎖定整個表格進行寫入。但我更喜歡那個時候只讀事務不會被阻塞。

我在客戶端程序和IBM DB2免費版中使用的ODBC與libodbc++ wrapper for C++(儘管數據庫選擇可能仍會更改)。這就是我想這樣做的:

  • 開始在連接自動提交和默認隔離模式
  • 當需要添加一個新行,設置自動提交虛假和隔離模式連載
  • 之前和新的鍵值後
  • 計算讀取行和插入新行
  • 提交
  • 返回到自動提交和默認隔離模式

這會做這份工作嗎?是否允許其他交易同時閱讀?還有其他更好的方法嗎?

順便說一句,我沒有在libodbC++ i/f中看到一種指定只讀事務的方式。在odbc中可能嗎?

編輯:感謝非常有用的答案,我有麻煩選擇一個。

回答

2

如果您的數據庫處於SERIALIZABLE模式,則根本沒有任何問題。給定一個關鍵K,要獲取上一個和下一個鍵,您必須運行以下查詢:

select key from keys where key > K order by key limit 1;  # M? 
select key from keys where key < K order by key desc limit 1; # I? 

上述工作在MySQL中。此等效的查詢DB2中的工作(從註釋):

select key from keys where key = (select min(key) from keys where key > K); 
select key from keys where key = (select max(key) from keys where key < K); 

第一個查詢設置的範圍內鎖,用於防止其它事務插入於K大於鍵更大和小於或等於M.

第二個查詢設置範圍鎖定,以防止其他事務插入小於K且大於或等於I的密鑰。

主鍵上的唯一索引可防止K插入兩次。所以你完全被覆蓋了。

這是交易的內容;所以你可以編寫你的代碼,就好像整個數據庫被鎖定一樣。

注意:這需要一個支持真正可串行化的數據庫。幸運的是,DB2的確如此。其他支持真正可串行化的DBMS:SQLServer和MySQL/InnoDB。 DBMS不支持:Oracle,PostgreSQL!

+1

什麼是「真正的可串行性」? – Quassnoi 2010-11-11 22:30:54

+0

謝謝。不幸的是,DB2不支持「LIMIT 1」。我使用'ORDER BY'並取第一行。它會鎖定整個桌子嗎?我想把它改成'SELECT * from mytable WHERE key =(SELECT max(key)FROM mytable where key davka 2010-11-14 10:44:29

+0

只修復了我的查詢;忘記了ORDER BY子句。 '''SELECT max(key)FROM mytable其中鍵 2010-11-15 01:59:28

1

如果您的數據庫和存儲引擎允許這樣做,那麼您應該爲您嘗試在其間插入的兩行發出SELECT FOR UPDATE

這會與任何併發的SELECT FOR UPDATE衝突。

缺點是,行1012(插入11)的鎖定也將阻止選擇810(插入9)。

InnoDB in MySQL還可以對索引鎖定一個next-key鎖定,即鎖定索引記錄和下一條記錄之間的差距。

在這種情況下,您只需在第一行上發出SELECT FOR UPDATE,並在此之前同時插入一行。

但是,這需要強制索引並在索引上提供range條件,根據您的查詢可能或不可能。

+0

非常感謝!請澄清幾點:我想我需要進入「手動提交」模式,對吧?默認隔離模式(Read Committed)對此是否正常?其他'SELECT'(不適用於更新)語句是否能夠讀取鎖定的行? – davka 2010-11-11 14:31:58

+0

@davka:手工提交,絕對。對於'InnoDB',默認隔離模式是'REPEATABLE READ',這是間隙鎖定所必需的,如果你不需要它們,任何隔離模式都可以。沒有'FOR UPDATE'子句的併發語句將能夠看到鎖定的記錄(在'SQL Server'中,你需要啓用'SNAPSHOT ISOLATION')。 – Quassnoi 2010-11-11 14:35:30

+0

雖然SQL標準建議使用SERIALIZABLE。在這種情況下,您不需要執行SELECT FOR UPDATE。 – 2010-11-11 20:39:09

1

您的一般方法是正確的。但是你應該使用一個SELECT語句來覆蓋這兩行以及它們之間的所有可能的行。例如:

SELECT * FROM MYTABLE WHERE PKCOL BETWEEN 6 AND 10 

與悲觀鎖和事務隔離級別序列化,這個SELECT語句應防止新的行插入,將改變SELECT的結果數據庫系統。

相關問題