有兩種常用的鎖定方法。
首先,你有悲觀鎖定。在這種方法中,您鎖定了阻止其他人更改該行的行(SELECT ... FOR UPDATE
)。然後你做UPDATE
。當您提交更改時,鎖定被釋放。在這種情況下,不需要有版本號/時間戳列(至少不支持鎖定),代碼相對容易。
悲觀鎖定的不利之處在於,您需要鎖定用戶在可能編輯數據的頁面上的整個時間。由於HTTP是無狀態協議,因此如果您要構建基於Web的應用程序,這在技術上確實很難。最初呈現頁面的請求通常會從連接池中獲取連接,請執行SELECT
,然後在頁面完成後將連接返回到池。後續更新數據的請求通常會發生在與另一個數據庫會話不同的連接上,因此您無法在第一個會話中鎖定該行並在第二個會話中進行更新。如果您想悲觀地鎖定該行,則需要在後端執行大量工作,以確保一個數據庫連接與特定中間層會話綁定,直到用戶完成編輯數據爲止。這通常會對可伸縮性產生很大的負面影響,並且會引入各種會話管理問題 - 例如,您如何知道我是否請求了一個頁面,鎖定了一行,然後在沒有註銷或更改的情況下關閉了瀏覽器?你將離開鎖定在數據庫中的記錄多久?如果其他會話嘗試鎖定該行,會發生什麼情況?如果第一個人出去吃午飯,你會讓這個會議等待鎖定多久?通常,人們不會在基於Web的應用程序中實施悲觀鎖定,因爲管理會話和會話狀態太不切實際了。
第二個選項是樂觀鎖定。在這種方法中,您可以向該行添加版本號/時間戳。您在查詢數據時選擇此版本號/時間戳。然後,當您稍後進行更新並檢查有多少行被實際修改時,您可以在WHERE
子句中使用它。如果您只修改一行,您知道自從您閱讀該行後該行沒有更改。如果您修改0行,您知道該行確實發生了變化,您可以處理該錯誤。
因此,舉例來說,你會選擇數據與版本號一起
SELECT address_line1, city, state, zip, version
FROM addressTable
WHERE address_id = `<<some key>>`
當你準備做更新,你會在那裏你在使用version
做這樣的事你UPDATE
如果該行改變
UPDATE addressTable
SET address_line1 = `<<new address line 1>>`,
city = `<<new city>>`,
state = `<<new state>>`,
zip = `<<new zip>>`,
version = version + 1
WHERE address_id = `<<some key>>`
AND version = `<<version you read initially>>`
IF(SQL%ROWCOUNT = 0)
THEN
-- Darn. The row must have changed since you read it. Do something to
-- alert the user. Most likely, the application will need to re-query the
-- data to see what the address has been changed to and then ask the user
-- whether they want to re-apply the changes.
RAISE_APPLICATION_ERROR(-20001, 'Oops, the row has changed since you read it.');
END;
您的應用程序,然後會做一些與錯誤有用拋出一個錯誤。通常,這意味着再次查詢數據,向用戶展示更改,並詢問他們是否仍然想要應用其更改。例如,如果我讀取地址並開始編輯它,請去午餐,我的同事登錄,讀取相同的地址,進行一些編輯並保存,然後返回並嘗試保存我的更改,這通常是有意義的告訴我,我的同事已經將地址更改爲新的東西 - 我想繼續進行編輯還是要放棄它們?
SELECT FOR UPDATE,但我不認爲這是一個樂觀的鎖定... – Vadim
真的嗎?你能詳細說明選擇更新嗎?我認爲這鎖定了行? – wonderBoy322
@ wonderBoy322它鎖定了行,但我會考慮悲觀鎖定。這是「悲觀的」,因爲當你確信別人會嘗試修改相同的行時使用它。相對於「樂觀」,並假設沒有其他人會修改同一行,所以你做了所有的工作,然後在最後檢查某種標誌以確保其他人沒有改變任何東西。 –