MySQL中的八種鎖!你知道幾種?
前言
在雙11期間,支付寶數據庫集群每秒處理25萬筆交易,而支撐這一切的核心技術之一就是MySQL的鎖機制。
很多小伙伴在工作中都遇到過這樣的場景:
- 凌晨批量處理數據時系統突然卡死。
- 高并發場景下出現詭異的死鎖報錯。
- 明明只更新一行卻導致全表阻塞。
這篇文章跟大家一起聊聊MySQL的8種鎖,希望對你會有所幫助。
一、鎖的本質:并發控制的基石
1.為什么需要鎖?
當多個事務同時操作同一數據時,可能引發:
- 臟讀:讀到未提交的數據
- 不可重復讀:同事務內兩次讀取結果不同
- 幻讀:同條件查詢出現新記錄
鎖的作用:通過對數據資源加鎖,實現事務的隔離性(ACID中的"I")
二、鎖的分類全景圖
1.按粒度劃分
按粒度劃分為:
- 表鎖
- 頁鎖
- 行鎖
2.按模式劃分
鎖類型 | 共享性 | 典型場景 |
共享鎖(S) | 可共享 | SELECT ... LOCK IN SHARE MODE |
排他鎖(X) | 獨占 | UPDATE/DELETE/INSERT |
意向共享鎖(IS) | 表級標記 | 準備加行級S鎖前 |
意向排他鎖(IX) | 表級標記 | 準備加行級X鎖前 |
三、行級鎖:高并發的核心戰場
1.記錄鎖(Record Lock)
鎖定索引記錄:
-- 事務A
BEGIN;
SELECT * FROM users WHERE id = 1 FOR UPDATE; -- 對id=1加X鎖
-- 事務B(將被阻塞)
UPDATE users SET name = 'Tom' WHERE id = 1;
底層實現:
2.間隙鎖(Gap Lock)
鎖定索引區間(解決幻讀):
假設當前表結構:id主鍵(當前有id=1,5,10)
BEGIN;
SELECT * FROM users WHERE id BETWEEN 5 AND 10 FOR UPDATE;
-- 阻塞所有[5,10]區間的插入
INSERT INTO users(id) VALUES(6); -- 被阻塞!
INSERT INTO users(id) VALUES(11); -- 成功
鎖定范圍:
3.臨鍵鎖(Next-Key Lock)
記錄鎖+間隙鎖組合:
假設當前數據庫隔離級別是RR(Repeatable Read):
BEGIN;
SELECT * FROM users WHERE id > 5 FOR UPDATE;
-- 阻塞操作
UPDATE users SET name='A' WHERE id=10; -- 記錄鎖阻塞
INSERT INTO users(id) VALUES(6); -- 間隙鎖阻塞
鎖范圍示意圖:
四、表級鎖:全表掃描的保護傘
1.表鎖(Table Lock)
顯式加鎖:
LOCK TABLES users WRITE; -- 獲取寫鎖
-- 執行更新...
UNLOCK TABLES;
隱式加鎖(DDL操作自動加鎖):
ALTER TABLE users ADD COLUMN age INT; -- 自動加表級X鎖
2.元數據鎖(MDL)
保護表結構:
-- 會話A
BEGIN;
SELECT * FROM users; -- 獲取MDL讀鎖
-- 會話B(被阻塞)
ALTER TABLE users ADD COLUMN email VARCHAR(255);
等待鏈:
五、死鎖:高并發的終極挑戰
1.經典死鎖場景
-- 事務A
BEGIN;
UPDATE accounts SET balance = balance - 100WHEREid = 1;
UPDATE accounts SET balance = balance + 100WHEREid = 2;
-- 事務B(反向操作)
BEGIN;
UPDATE accounts SET balance = balance - 100WHEREid = 2;
UPDATE accounts SET balance = balance + 100WHEREid = 1;
死鎖形成過程:
2.死鎖檢測與解決
自動檢測:
SHOW ENGINE INNODB STATUS;
-- 查看LATEST DETECTED DEADLOCK
手動處理:
// Spring事務重試
@Retryable(maxAttempts = 3, backoff = @Backoff(delay = 100))
@Transactional
public void transferMoney(Long from, Long to, BigDecimal amount) {
// 轉賬邏輯
}
六、鎖監控與優化實戰
1.鎖等待分析
-- 查看鎖等待
SELECT * FROM information_schema.INNODB_TRX;
SELECT * FROM information_schema.INNODB_LOCKS;
SELECT * FROM information_schema.INNODB_LOCK_WAITS;
2.索引優化避免全表鎖
問題SQL:
UPDATE users SET status=1 WHERE name LIKE 'A%'; -- 無索引導致表鎖
優化方案:
ALTER TABLE users ADD INDEX idx_name(name); -- 創建索引
UPDATE users SET status=1 WHERE name LIKE 'A%'; -- 僅加行鎖
3.鎖超時配置
# my.cnf
[mysqld]
innodb_lock_wait_timeout=50 # 默認50秒
七、不同隔離級別的鎖差異
隔離級別 | 臟讀 | 不可重復讀 | 幻讀 | 鎖機制 |
讀未提交(Read Uncommitted) | 可能 | 可能 | 可能 | 不加鎖 |
讀已提交(Read Committed) | 不可能 | 可能 | 可能 | 語句級快照 |
可重復讀(Repeatable Read) | 不可能 | 不可能 | 可能(*) | 臨鍵鎖(默認) |
串行化(Serializable) | 不可能 | 不可能 | 不可能 | 全表鎖 |
InnoDB在RR級別通過Next-Key Lock解決幻讀問題。
八、鎖機制最佳實踐
1. 鎖優化口訣
- 一快:事務執行要快。
- 二小:鎖粒度盡量小。
- 三避免:避免大事務、全表掃描、長等待 。
2. 不同場景鎖選擇
場景 | 推薦方案 |
精確更新單行 | 行級X鎖(WHERE主鍵) |
范圍更新 | Next-Key Lock(RR隔離級別) |
全表更新 | 分批提交+低峰期執行 |
結構變更 | PT-Online-Schema-Change工具 |
總結
- 鎖是雙刃劍:保護數據一致性的同時降低并發度
- 粒度決定性能:行鎖 > 頁鎖 > 表鎖
- 隔離級別是基礎:根據業務選擇合適級別(推薦RR)
- 索引是鑰匙:80%的鎖問題可通過優化索引解決
- 監控是眼睛:善用
SHOW ENGINE INNODB STATUS
正如數據庫專家Michael Stonebraker所言:“The best locking strategy is no locking at all.”
最高明的鎖策略是“無鎖”,而這正是我們不斷優化的方向。