MySQL 併發控制 -- 讀鎖、寫鎖、樂觀鎖
併發是一個讓人很頭疼的問題,通常我們會在服務端或者資料庫端做處理,保證在併發下資料的準確性,今天我們簡要的討論一下MySQL中如何通過鎖解決併發問題
讀鎖
-
也叫共享鎖 (shared lock)
-
如何使用
SELECT * FROM table_name WHERE ... LOCK IN SHARE MODE
-
詳解
即事務A 使用共享鎖 獲取了某條(或者某些)記錄時,事務B 可以讀取這些記錄,可以繼續新增共享鎖,但是不能修改或刪除這些記錄(當事務B 對這些資料修改或刪除時會進入阻塞狀態,直至鎖等待超時或者事務A提交)
-
使用場景
讀取結果集的最新版本,同時防止其他事務產生更新該結果集
主要用在需要資料依存關係時確認某行記錄是否存在,並確保沒有人對這個記錄進行UPDATE或者DELETE操作
-
注意事項
當使用讀鎖時,避免產生如下操作
事務1 BEGIN; select * from sys_user where id = 1 LOCK IN SHARE MODE; (步驟1) update sys_user set username = "taven" where id = 1; (步驟3,發生阻塞) COMMIT;
事務2 BEGIN; select * from sys_user where id = 1 LOCK IN SHARE MODE; (步驟2) update sys_user set username = "taven" where id = 1; (步驟4,死鎖) COMMIT;
-
分析
根據我們之前對讀鎖定義可知,當有事務拿到一個結果集的讀鎖時,其他事務想要更新該結果集,需要拿到讀鎖的事務提交(釋放鎖)。而上述情況兩個事務分別拿到了讀鎖,而且都有update 操作,兩個事務互相等待造成死鎖(都在等待對方釋放讀鎖)
寫鎖
-
也叫排它鎖(exclusive lock)
-
如何使用
SELECT * FROM table_name WHERE ... FOR UPDATE
-
詳解
一個寫鎖會阻塞其他的讀鎖和寫鎖
即事務A 對某些記錄新增寫鎖 時,事務B 無法向這些記錄新增寫鎖或者讀鎖(不新增鎖的讀取是可以的),事務B 也無法執行對 鎖住的資料 update delete
-
使用場景
讀取結果集的最新版本,同時防止其他事務產生讀取或者更新該結果集 。
例如:併發下對商品庫存的操作
-
注意事項
在使用讀鎖、寫鎖時都需要注意,讀鎖、寫鎖屬於行級鎖。即事務1 對商品A 獲取寫鎖,和事務2 對商品B 獲取寫鎖互相不會阻塞的。需要我們注意的是我們的SQL要合理使用索引 ,當我們的SQL 全表掃描的時候,行級鎖會變成表鎖 。
使用
EXPLAIN
檢視 SQL是否使用了索引,掃描了多少行
樂觀鎖
-
上述介紹的是行級鎖 ,可以最大程度地支援併發處理(同時也帶來了最大的鎖開銷)樂觀鎖是一種邏輯鎖,通過資料的版本號(vesion)的機制來實現,極大降低了資料庫的效能開銷。
-
我們為表新增一個欄位 version,讀取資料時將此版本號一同讀出,之後更新時,對此版本號+1 ,同時將提交資料的version 與資料庫中對應記錄的當前version 進行比對,如果提交的資料版本號大於資料庫表當前版本號,則予以更新,否則認為是過期資料
update t_goods set status=2,version=version+1 where id=#{id} and version < #{version}; // 更新前將version自增
或者
update t_goods set status=2,version=version+1 where id=#{id} and version = #{version}; // 更新前version 不自增