引言
在數據庫并發控制中,鎖機制是保障數據一致性和隔離性的核心手段。MySQL中意向鎖、間隙鎖以及next-key鎖等復雜鎖類型,旨在協調表級鎖與行級鎖之間的關系,防止數據的臟讀、不可重復讀和幻讀現象,尤其是在可重復讀隔離級別下發揮關鍵作用。然而,鎖機制在提升數據隔離性的同時,也可能帶來死鎖風險和寫傾斜問題,影響系統的并發性能和業務邏輯正確性。本文將系統梳理MySQL中意向鎖與next-key鎖的原理及應用,剖析間隙鎖導致的死鎖現象,并結合實際案例探討寫傾斜問題及其解決方案,幫助讀者深入理解數據庫鎖機制與事務隔離的內在聯系。
意向鎖
主要作用是協調表鎖與行鎖之間的互斥關系。意向鎖本身之間是不會發生沖突的,與行級別的共享鎖(S 鎖)和排他鎖(X 鎖)也不發生沖突。它們主要與表級別的共享鎖(S 鎖)和排他鎖(X 鎖)發生沖突,在表級別加鎖時提升效率,避免全表掃描來判斷是否有行鎖存在。
- 意向共享鎖(IS, Intention Shared Lock):當事務打算對表中的某些行加共享鎖(S 鎖)時,必須首先對該表加一個意向共享鎖(IS 鎖),告訴其他想鎖表的事務該表有行數據加了共享鎖。
- 意向排他鎖(IX, Intention Exclusive Lock):當事務打算對表中的某些行加排他鎖(X 鎖)時,必須首先對該表加一個意向排他鎖(IX 鎖),告訴其他想鎖表的事務該表有行數據加了排他鎖。
next-key Lock的理解
next-key Lock = gap Lock + row Lock,只在可重復讀隔離級別存在。
- 對主鍵或唯一索引,如果當前讀時,where條件全部精確命中(=或者in),這種場景本身就不會出現幻讀,只會加行記錄鎖。如果查詢未精準命中,查詢了不存在的值,會產生間隙鎖。例如:[1, 5, 10]。select * from table where id=3; 會對(1, 5)(左開右開)加上鎖。
- 沒有索引的列,當前讀操作時,會加全表gap鎖,生產環境要注意。
- 非唯一索引列,如果where條件部分命中(>、<、like等)或者全未命中,則會加附近gap間隙鎖。例如,某表數據如下,非唯一索引[2、6、9、9、11、15]。要操作非唯一索引列 9 的數據(select * from table where x = 9 for update),gap鎖將會鎖定的列是(6, 11](左開右閉),該區間內無法插入數據。
- 主鍵或唯一索引,范圍查詢時會對查找范圍內的間隙上鎖,例如,唯一索引[1、5、10、15]。where id>1 and id<10 for update,gap鎖會對(1,10)(左開右開),該區間會加鎖。
next-key Lock無法解決的幻讀
next-key Lock可以解決當前讀下的幻讀問題,但引入了死鎖問題。next-key Lock無法解決當前讀隔離級別下的幻讀問題。
間隙鎖導致的死鎖問題
間隙鎖之間不互斥,間隙鎖和寫鎖之間是互斥的。
寫傾斜問題
當兩個或多個并發事務讀取相同的數據并基于這些讀取的結果執行寫操作時,因為這些事務在執行過程中彼此不可見,最終可能導致違反數據一致性或業務邏輯的結果。
舉個例子
假設,醫院調度系統,每個醫生在一天內只能處理最多 10 個病人。
- 事務 A 和事務 B 都讀取到醫生1的病人數量是 9。
- 事務 A 判斷病人數量小于 10,因此增加一個病人,病人數量變為 10,并提交事務。
- 事務 B 仍然認為病人數量是 9(因為它讀取時事務 A 尚未提交),也增加一個病人,病人數量變為 11,并提交事務。
#表結構
CREATE TABLE doctor_patients (doctor_id INT,patient_count INT,PRIMARY KEY (doctor_id)
);#事務A
START TRANSACTION;
SELECT patient_count FROM doctor_patients WHERE doctor_id = 1; -- 讀取到的病人數量是 9
-- 邏輯判斷:如果病人數量小于10,則增加一個病人
UPDATE doctor_patients SET patient_count = patient_count + 1 WHERE doctor_id = 1;
COMMIT;#事務B
START TRANSACTION;
SELECT patient_count FROM doctor_patients WHERE doctor_id = 1; -- 讀取到的病人數量也是 9(因為事務 A 尚未提交)
-- 邏輯判斷:如果病人數量小于10,則增加一個病人
UPDATE doctor_patients SET patient_count = patient_count + 1 WHERE doctor_id = 1;
COMMIT;
解決辦法
- 開啟串行化隔離級別,事務A對行數據加共享鎖,事務B也可以對行數據加共享鎖。事務A修改行數據時發現事務B加了共享鎖只能阻塞,事務B修改行數據時發現事務A加了共享鎖此時產生了死鎖。保證了不會產生鎖傾斜問題。
- 讀數據時加上for update,保證其他事務無法讀取這行數據。
感謝您的閱讀!如果文章中有任何問題或不足之處,歡迎及時指出,您的反饋將幫助我不斷改進與完善。期待與您共同探討技術,共同進步!