樂觀鎖是 Optimistic Lock 的直譯,依據本系列對於悲觀鎖的說明可知Lock(鎖)的意義是:「為了避免資料衝突,某一使用者或程序A可以在讀取或寫入資源前,先將資源上鎖」。
資料庫裡的「壞事」是指:多人或多程序使用同資源時,資料會衝突或發生不一致的情況。相對於悲觀鎖假設「壞事」比較可能發生,樂觀鎖則假設「壞事」比較不可能會發生,即假設「資料變動不頻繁及衝突不易發生」。
所以樂觀鎖是:
以「資料變動不頻繁及衝突不易發生」為前提,在高並發情境下,避免資料衝突的機制。
特性
機制
相較於悲觀鎖保證資料在同一時間只能被一個事務(transaction)內的操作修改,樂觀鎖在實際進行修改提交前,允許多個事務同時進行讀寫操作。某個事務提交修改前,會透過版本號及時間戳檢查資料是否在這個事務開始後才被其他事務修改。如果有被修改過的話,系統會回滾當前這個事務或在最新的修改結束後再重試。
舉例而言,假設有2個人同時購買同一商品,同時開啟了事務A跟事務B,事務A先完成後修改了版本號,事務B就無法用原版本號修改資料庫、完成購買。
優點
- 提升吞吐量:因為多個事務可以同時進行讀寫,不需排隊等候。
- 簡化鎖的管理:因為事務間獨立運作,所以較不需管理事務間獲取鎖的順序。
缺點
- 增加資料衝突發生的機率:開發者需要考慮重試等資料衝突處理機制,處理複雜的業務邏輯。
- 不保證先到先得:因為事務間獨立運作,沒有排序關係。
- 未必適用於各種業務場景:因為樂觀鎖的實踐是人為定義的。
適用情境
在讀取功能為主的系統中,由於事務衝突不頻繁,使用樂觀鎖可提升吞吐量。在寫入功能的情境下,如果每個事務執行的時間很短,衝突不易發生,相較於悲觀鎖,使用樂觀鎖可以減少事務等待的時間。
資料庫實作
這邊是用MySQL的Workbench來演示扣庫過程樂觀鎖的應用。假設現在有一張資料表book,欄位stock記錄著書籍的庫存。
按照以下步驟模擬多個事務同時扣庫的情形,而在這個工具中,多個事務需透過多個DB Session開啟:
- 先開啟2個Session分別為Session A 跟 Session B,分別查詢id是1的書籍,找出庫存數量與版本號。
SELECT version, stock FROM bookstore.book where book_id = 1;
兩個Session的查詢結果應會一致,假設是:
version | stock |
---|---|
1 | 92 |
- Session A 執行扣除庫存的SQL,可由返回結果確認有一筆資料修改成功。
update bookstore.book set version = version + 1, stock = stock - 1 where version = 1 and book_id = 1;
此時用查詢SQL檢查版本號與數量,結果是:
version | stock |
---|---|
2 | 91 |
- Session B 用與Session A 相同的SQL扣除庫存,可由返回結果確認沒有符合條件(版本號為1)的資料,所以也沒有資料被修改。
update bookstore.book set version = version + 1, stock = stock - 1 where version = 1 and book_id = 1;
此時用查詢SQL檢查版本號與數量,結果也與Session A 相同,再次確認扣庫未完成。
version | stock |
---|---|
2 | 91 |