2012-01-27 88 views
5

我有以下情形:
「FOR UPDATE」 V/S「鎖定共享模式」:允許併發線程讀取鎖定行的更新「狀態」值

  • 用戶X登錄到應用來自位置lc1:打電話Ulc1
  • 用戶X(已被黑客入侵,或者他的某個朋友知道他的登錄憑據,或者他只是從他的機器上的其他瀏覽器登錄,等等)。從位置lc2同時登錄:呼叫Ulc2

我使用的是主要的servlet其中:
- 會從數據庫彙集
連接 - 設置自動提交虛假
- 執行,通過應用層進了命令:如果一切順利,設置自動提交到真正的「最後「聲明,並關閉連接。否則,如果發生異常,則使用rollback()。

在我的數據庫(mysql/innoDb)我有一個「歷史」表,與行列:
id(主鍵)|用戶名|日期|主題|鎖定

列「鎖定」的默認值爲「false」,它用作標記特定行是否被鎖定的標誌。
每一行是特定於用戶(如u可以從用戶名欄中看到)

所以回到場景:
- > ULC1從數據庫發送命令到更新他的歷史記錄日期「 D「和主題」T「。

- >從ULC2在確切同時分貝爲相同日期「d」和相同主題「T」發送相同命令更新歷史。

我想要實現的MySQL的/ InnoDB的鎖定系統,使任何一個線程到達做以下檢查:

是列「鎖定」此行真的還是假的?

  • 如果爲true,返回一個消息給用戶
  • 如果不是真(即未鎖定)「他已經從另一個位置更新相同的數據」:將其標記爲鎖定和更新,然後復位鎖定假一次完成。

這兩種mysql鎖定技術中的哪一種會真正允許第二個到達的線程讀取鎖定列的「更新」值來決定採取的wt操作?

我應該使用「FOR UPDATE」「LOCK IN SHARE MODE」

這種情況說明了什麼我要完成:
- ULC1線程到達第一:列「鎖定」是假的,將其設置爲true,並繼續更新過程
- ULC2線程到達而ULC1的交易仍在進行中,並即使該行通過innoDb功能被鎖定,也不必等待,但實際上讀取了「鎖定」列的「新」值,即「真」,因此事實上不必等到Ulc1事務提交讀取「鎖定」列的值(到那時爲止該列的值已經被重置爲假)。

我對2種鎖定機制並不是非常有經驗,到目前爲止我的理解是鎖定共享模式允許其他事務讀取鎖定的行,而FOR UPDATE甚至不允許讀取。但是這個讀取得到更新的值嗎?或第二個到達的線程必須等待第一個線程提交然後讀取值?

任何關於哪種鎖定機制用於這種情況的建議值得讚賞。
另外,如果有更好的方法來檢查行是否被鎖定(除了使用真/假列標誌),請讓我知道它。
謝謝

SOLUTION (基於的jdbc僞例如@ Darhazer的答案)

表:[ID(主鍵)|用戶名|日期|主題|鎖定]

connection.setautocommit(false); 
//transaction-1 
PreparedStatement ps1 = "Select locked from tableName for update where id="key" and locked=false); 
ps1.executeQuery(); 

//transaction 2 
PreparedStatement ps2 = "Update tableName set locked=true where id="key"; 
ps2.executeUpdate(); 
connection.setautocommit(true);// here we allow other transactions threads to see the new value 

connection.setautocommit(false); 
//transaction 3 
PreparedStatement ps3 = "Update tableName set aField="Sthg" where id="key" And date="D" and topic="T"; 
ps3.executeUpdate(); 

// reset locked to false 
PreparedStatement ps4 = "Update tableName set locked=false where id="key"; 
ps4.executeUpdate(); 

//commit 
connection.setautocommit(true); 

回答

5

SHARE模式鎖定將允許第二線程讀取值,但實際值將是查詢(讀COMMITED)前一個或前交易(可重複讀)已經啓動(因爲MySQL使用多版本化;第二個事務必須看到的是由隔離級別定義的)。因此,如果第一個事務在讀取時未提交,則會讀取舊值。

在您的場景中,最好有1個事務使用select for update鎖定記錄,另一個在記錄上工作,在提交/回滾時使用第三個解鎖記錄。

帶有select for update的第二個線程事務將等待第一個完成,然後將讀取實際值並決定不繼續其他事務,但通知用戶該記錄已鎖定。

爲避免死鎖,請確保您正在使用唯一索引執行select for update

示例代碼:

connection.setautocommit(false); 
//transaction-1 
PreparedStatement ps1 = "Select locked from tableName for update where id="key" and locked=false); 
ps1.executeQuery(); 

//transaction 2 
PreparedStatement ps2 = "Update tableName set locked=true where id="key"; 
ps2.executeUpdate(); 
connection.setautocommit(true); // here we allow other transactions/threads to see the new value 

connection.setautocommit(false); 
//transaction 3 
PreparedStatement ps3 = "Update tableName set aField="Sthg" where id="key" And date="D" and topic="T"; 
ps3.executeUpdate(); 

// probably more queries 

// reset locked to false 
PreparedStatement ps4 = "Update tableName set locked=false where id="key"; 
ps4.executeUpdate(); 

//commit 
connection.setautocommit(true); 
+0

THX對您有所幫助。但是我沒有得到你提到第二個線程的部分:你的意思是第二個線程能夠在線程1解鎖記錄之前讀取鎖定的更新值。 – shadesco 2012-02-02 13:57:44

+0

@shadesco當你已經將它標記爲真時,這個想法並不是鎖定已鎖定的值。記錄被鎖定。將數據庫中的鎖定時間保持在最小值並在應用程序中執行(如果您鎖定整個事務處理時間的記錄,則根本沒有必要具有「鎖定」值)。您也可以鎖定記錄,但在將更改提交到鎖定列後,其他事務將看到新值。 – 2012-02-02 15:11:11

+0

我想我明白了你的意思,但是我寫了一個小小的更新部分,附有一個問題給你,你可以檢查一下嗎? – shadesco 2012-02-02 16:23:30