寫在前面
在 MySQL 數據庫中,鎖機制是保障并發控制和數據一致性的關鍵。合理運用鎖機制,能有效避免數據競爭,提升數據庫性能。接下來,我們就深入了解 MySQL 中的各類鎖。
博主總結(注:針對總結的詳解補充在后面)
- 鎖機制
- mysql里有哪些鎖?
- 全局鎖:通過flush tables with read lock 語句會將整個數據庫就處于只讀狀態了,這時其他線程執行以下操作,增刪改或者表結構修改都會阻塞。全局鎖主要應用于做全庫邏輯備份,這樣在備份數據庫期間,不會因為數據或表結構的更新,而出現備份文件的數據與預期的不一樣。
- 表級鎖
- read、write實現讀寫鎖也就是共享鎖、排他鎖。
- ①表鎖:通過lock tables 語句可以對表加表鎖,表鎖除了會限制別的線程的讀寫外,也會限制本線程接下來的讀寫操作。對于 InnoDB 引擎,無索引的 UPDATE/DELETE 可能會導致鎖升級為表鎖。
- ②元數據鎖:當我們對數據庫表進行操作時,會自動給這個表加上 MDL鎖,對一張表進行 CRUD 操作時,加的是 MDL 讀鎖;對一張表做結構變更操作的時候,加的是 MDL 寫鎖;MDL 是為了保證當用戶對表執行 CRUD 操作時,防止其他線程對這個表結構做了變更。
- ③意向鎖:意向鎖是一種表級鎖,表示事務打算對表中的某些行數據加鎖,但不會直接鎖定數據行本身。當執行 SELECT ... LOCK IN SHARE MODE 時,會自動加意向共享鎖;當執行 SELECT ... FOR UPDATE 時,會自動加意向排他鎖。意向鎖之間互相兼容,也不會與行鎖沖突。
- 作用:意向鎖的目的是為了快速判斷表里是否有記錄被加鎖。在沒有意向鎖的情況下,當事務 A 持有某表的行鎖時,如果事務 B 想添加表鎖,InnoDB 必須檢查表中每一行數據是否被加鎖,這種全表掃描的方式效率極低。有了意向鎖之后,事務在加行鎖前,先在表上加對應的意向鎖;其他事務加表鎖時,只需檢查表上的意向鎖,無需逐行檢查。
- 作用:意向鎖的目的是為了快速判斷表里是否有記錄被加鎖。在沒有意向鎖的情況下,當事務 A 持有某表的行鎖時,如果事務 B 想添加表鎖,InnoDB 必須檢查表中每一行數據是否被加鎖,這種全表掃描的方式效率極低。有了意向鎖之后,事務在加行鎖前,先在表上加對應的意向鎖;其他事務加表鎖時,只需檢查表上的意向鎖,無需逐行檢查。
- 行級鎖:InnoDB 引擎是支持行級鎖的,而 MyISAM 引擎并不支持行級鎖。行鎖是 InnoDB 存儲引擎中最細粒度的鎖,它鎖定表中的一行記錄,允許其他事務訪問表中的其他行。底層是通過給索引加鎖實現的,這就意味著只有通過索引條件檢索數據時,InnoDB 才能使用行級鎖,否則會退化為表鎖。
- 通過 SELECT ... FOR UPDATE 可以加排他鎖。通過 SELECT ...LOCK IN SHARE MODE 可以加共享鎖。
- ①記錄鎖,鎖住的是一條記錄。而且記錄鎖是有 S 鎖和 X 鎖之分的,滿足讀寫互斥,寫寫互斥
- ②間隙鎖,只存在于可重復讀隔離級別,目的是為了解決可重復讀隔離級別下幻讀的現象。
- ③Next-Key Lock 稱為臨鍵鎖,是 Record Lock + Gap Lock 的組合,鎖定一個范圍,并且鎖定記錄本身。
- MySQL的樂觀鎖和悲觀鎖了解嗎?
- MySQL 中的行鎖和表鎖都是悲觀鎖。
- 悲觀鎖是一種"先上鎖再操作"的保守策略,它假設數據被外界訪問時必然會產生沖突,因此在數據處理過程中全程加鎖,保證同一時間只有一個線程可以訪問數據。
- 樂觀鎖會假設并發操作不會總發生沖突,屬于小概率事件,因此不會在讀取數據時加鎖,而是在提交更新時才檢查數據是否被其他事務修改過。樂觀鎖并不是 MySQL 內置的鎖機制,而是通過程序邏輯實現的,常見的實現方式有版本號機制和時間戳機制。通過在表中增加 version 字段或者 timestamp 字段來實現。
- 遇到過MySQL死鎖問題嗎,你是如何解決的?
- 遇到過。MySQL 的死鎖是由于多個事務持有資源并相互等待引起的。
- 我通過 SHOW ENGINE INNODB STATUS 查看死鎖信息,定位到是加鎖順序不一致導致的,最后通過調整加鎖順序解決了這個問題。
- MySQL兩個線程的update語句同時處理一條數據,會不會有阻塞?
- 會,行級鎖鎖住了。
- 具體例子來說,當事務A對id = 1這行記錄執行更新操作時,會在主鍵id為1的記錄上添加X類型(排他鎖)的記錄鎖。此時,如果事務B也嘗試對id = 1的記錄進行更新,由于發現該記錄已經被加鎖,事務B就會進入阻塞狀態,直到事務A提交或回滾,釋放鎖之后,事務B才能繼續執行。
- 兩條update語句處理一張表的不同的主鍵范圍的記錄,一個<10,一個>15,會不會遇到阻塞?底層是為什么的?
- 不會,臨鍵鎖沒有沖突。因為這兩個范圍沒有重疊部分,所以不存在鎖沖突。
- 具體例子來說,假設存在兩條UPDATE語句,分別處理主鍵范圍不同的記錄:
- 第一條UPDATE語句的條件是id < 10,它鎖定的范圍是(-∞, 10),即小于10的所有記錄及其間隙。
- 第二條UPDATE語句的條件是id > 15,它鎖定的范圍是(15, +∞),即大于15的所有記錄及其間隙。
- 如果2個范圍不是主鍵或索引?還會阻塞嗎?
- 會,因為第一個查詢觸發了全表查詢,相當于加了表鎖。這時第二個查詢就會阻塞。
- 具體解釋下,因為如果 update 沒有用到索引,在掃描過程中會對索引加鎖,所以全表掃描的場景下,所有記錄都會被加鎖,也就是這條 update 語句產生了 4 個記錄鎖和 5 個間隙鎖,相當于鎖住了全表。
- mysql里有哪些鎖?
———————————————————————————————————————————
———————————————————————————————————————————
實戰:解決 MySQL 死鎖問題
遇到死鎖時,可通過SHOW ENGINE INNODB STATUS查看詳細信息。曾遇到因加鎖順序不一致導致的死鎖,通過調整加鎖順序成功解決。
常見問題解答
- 兩個線程update同一數據:會阻塞,因行級排他鎖鎖定該行記錄,需等前一事務提交或回滾釋放鎖。
- 兩條update語句處理不同主鍵范圍記錄:若無重疊,臨鍵鎖無沖突,不會阻塞。
- 操作范圍無索引:會觸發全表掃描,相當于添加表鎖,導致后續操作阻塞。
掌握 MySQL 鎖機制,有助于在開發和運維中合理使用鎖,提升數據庫性能與穩定性。后續可進一步探索不同業務場景下的鎖優化策略,歡迎大家在評論區分享經驗!
比如在項目中,兩個事務分別更新兩張表,但是更新順序不一致。
-- 創建表/插入數據
CREATE TABLE account (id INT AUTO_INCREMENT PRIMARY KEY,balance INT NOT NULL
);INSERT INTO account (balance) VALUES (100), (200);-- 事務 1
START TRANSACTION;
-- 鎖住 id=1 的行
UPDATE account SET balance = balance - 10 WHERE id = 1;-- 等待鎖住 id=2 的行(事務 2 已鎖住)
UPDATE account SET balance = balance + 10 WHERE id = 2;-- 事務 2
START TRANSACTION;
-- 鎖住 id=2 的行
UPDATE account SET balance = balance - 10 WHERE id = 2;-- 等待鎖住 id=1 的行(事務 1 已鎖住)
UPDATE account SET balance = balance + 10 WHERE id = 1;
訪問相同的資源,但順序不同,就會導致死鎖。
解決辦法也很簡單,先使用?SHOW ENGINE INNODB STATUS\G;
?確認死鎖的具體信息,然后調整資源的訪問順序。