
1. 從一次線上事故說起為什么我們需要深入了解MySQL鎖那天晚上系統監控突然告警核心交易接口的響應時間從平時的幾十毫秒飆升到了十幾秒TPS斷崖式下跌。登錄服務器一看CPU和IO都挺正常但數據庫連接池幾乎全滿大量線程狀態顯示為Waiting for table metadata lock。緊急排查后發現是一個開發同學在業務低峰期執行了一個ALTER TABLE操作試圖給一個千萬級的大表加個索引。就是這個看似平常的DDL語句與業務高峰期仍在進行的查詢和更新操作產生了鎖沖突直接“鎖”住了整張表導致服務近乎癱瘓。最后我們不得不強制Kill掉那個DDL進程并調整了后續所有表結構變更的流程——必須在嚴格的維護窗口并且先評估鎖的影響。這次事故讓我深刻意識到對于使用MySQL的開發者、DBA甚至架構師而言僅僅會寫SQL是遠遠不夠的。“鎖”這個隱藏在數據庫引擎深處的并發控制機制平時悄無聲息一旦發威就足以讓整個系統“卡死”。很多人對鎖的理解停留在“表鎖”和“行鎖”這兩個名詞上或者背過“MyISAM用表鎖InnoDB用行鎖”這樣的面試題。但在真實的、高并發的生產環境中鎖的機制遠比這復雜和微妙。共享鎖、排他鎖、意向鎖、間隙鎖、臨鍵鎖、元數據鎖……這些鎖是如何協同工作的什么情況下行鎖會升級為什么我明明只更新一行數據卻感覺鎖住了半張表死鎖是怎么產生的又該如何避免和排查理解MySQL的鎖機制不是為了應付面試而是為了能設計出更健壯的數據模型編寫出更高效的SQL語句制定出更安全的運維策略。它就像數據庫世界的交通規則不懂規則遲早會出“車禍”。接下來我將結合多年的實戰經驗和案例分析為你層層剝開MySQL并發控制的神秘面紗不僅告訴你鎖是什么更告訴你它們為什么這樣工作以及如何在實踐中駕馭它們。2. 鎖的基石共享鎖與排他鎖的本質與交互要理解MySQL中紛繁復雜的鎖類型我們必須從最基礎、最核心的兩個概念入手共享鎖Shared Lock, S Lock和排他鎖Exclusive Lock, X Lock。這是所有鎖語義的基石。你可以把數據庫中的一條數據想象成一個會議室。共享鎖S鎖就像是對這個會議室的“讀取權限”。多個參與者可以同時持有這個會議室的S鎖一起進去查閱資料讀取數據互不干擾。在MySQL中普通的SELECT語句在默認的REPEATABLE READ隔離級別下并不會加鎖它是通過MVCC多版本并發控制來保證一致性的這點后面會詳述。但是如果你使用了SELECT ... LOCK IN SHARE MODE或者SELECT ... FOR SHAREMySQL 8.0就會對被讀取的行加上S鎖。排他鎖X鎖則像是這個會議室的“獨占使用權”。一旦一個參與者拿到了X鎖他就獨占了這間會議室其他任何人無論是想查閱還是想使用都不能再進入。在MySQL中UPDATE、DELETE、INSERT語句以及使用了SELECT ... FOR UPDATE的查詢都會對被操作的行加上X鎖。它們之間的兼容性規則是理解并發沖突的關鍵S鎖與S鎖是兼容的多個事務可以同時讀取同一行數據。S鎖與X鎖是不兼容的一個事務正在讀取某行持有S鎖另一個事務就不能修改它申請X鎖會被阻塞反之亦然一個事務正在修改某行持有X鎖其他事務也不能讀取它申請S鎖會被阻塞。X鎖與X鎖是不兼容的這很好理解兩個事務不能同時修改同一行數據。注意這里說的“讀取”指的是加鎖讀Locking Read。在InnoDB默認的RR隔離級別下普通的快照讀Snapshot Read是不加鎖的它通過ReadView訪問undo log中的歷史版本因此不會與寫操作沖突。這是MVCC帶來的巨大優勢也是高并發讀的保障。但務必分清“快照讀”和“當前讀加鎖讀”的區別。一個核心的實戰場景SELECT ... FOR UPDATEvsUPDATE很多人會混淆這兩個語句的加鎖行為。假設我們有一個賬戶余額表accounts現在要實現一個“查詢并扣款”的操作。錯誤示范邏輯漏洞-- 事務1 START TRANSACTION; SELECT balance FROM accounts WHERE user_id 1001; -- 假設查到100元 -- 在應用層判斷if (balance 50) { ... } UPDATE accounts SET balance balance - 50 WHERE user_id 1001; -- 扣款 COMMIT;在并發環境下事務1的SELECT快照讀和事務2的SELECT可能看到相同的余額100元都認為可以扣款然后相繼執行UPDATE。最終余額可能變成-50元這就是典型的“丟失更新”問題。正確做法使用悲觀鎖-- 事務1 START TRANSACTION; SELECT balance FROM accounts WHERE user_id 1001 FOR UPDATE; -- 對user_id1001的行加上X鎖 -- 此時其他事務對該行的SELECT ... FOR UPDATE或UPDATE都會被阻塞 UPDATE accounts SET balance balance - 50 WHERE user_id 1001; COMMIT;SELECT ... FOR UPDATE會對查找到的行施加X鎖從而在查詢的瞬間就“鎖定”了數據避免了并發修改。UPDATE語句本身也會在更新時對行加X鎖但SELECT ... FOR UPDATE的鎖是在UPDATE執行前就獲取的可以保證查詢和更新之間數據的一致性。這個例子揭示了鎖的第一個核心價值在并發環境中保護數據從“讀取”到“修改”這個關鍵窗口期的一致性。3. InnoDB的鎖升級意向鎖、行鎖與表鎖的協同當我們說“InnoDB支持行級鎖”時并不意味著它只有行鎖。為了實現高效的鎖管理InnoDB設計了一套多粒度的鎖機制包括意向鎖Intention Locks。為什么需要意向鎖想象一下如果沒有意向鎖事務A鎖定了表中的一行行級X鎖。此時事務B想給整個表加一個表級X鎖比如執行ALTER TABLE。事務B如何判斷自己能否加這個表鎖呢它必須逐行檢查表中是否有任何一行已經被加鎖。對于一個有上億行記錄的表這種檢查是災難性的、不現實的。意向鎖就是為了解決這個問題而生的。它是一種表級鎖但它表達的是一種“意向”Intention而不是真正的鎖定。意向共享鎖Intention Shared Lock, IS鎖事務打算給表中的某些行加S鎖。在給一行加S鎖之前必須先獲得該表的IS鎖。意向排他鎖Intention Exclusive Lock, IX鎖事務打算給表中的某些行加X鎖。在給一行加X鎖之前必須先獲得該表的IX鎖。規則是事務在獲取行鎖S或X之前必須先獲取對應表級的意向鎖IS或IX。意向鎖之間是兼容的因為它們只是表達意向不沖突。但意向鎖與真正的表級S/X鎖之間有特定的兼容性規則正是這套規則讓表級鎖的快速判斷成為可能。請求鎖類型 vs 已存在鎖類型X表IX表S表IS表X表沖突沖突沖突沖突IX表沖突兼容沖突兼容S表沖突沖突兼容兼容IS表沖突兼容兼容兼容從這個兼容性矩陣我們可以解讀出幾個關鍵點表級X鎖與任何意向鎖都沖突。這意味著如果一個事務持有了表級X鎖如LOCK TABLES ... WRITE其他事務連獲取意向鎖即打算操作任何行都不被允許。表級S鎖與IX鎖沖突。這意味著如果一個事務持有了表級S鎖如LOCK TABLES ... READ其他事務就不能獲取IX鎖即不能“打算”修改任何行。IX鎖與IX鎖是兼容的。這是實現高并發寫的關鍵兩個事務可以同時持有同一個表的IX鎖意味著它們可以同時修改表中不同的行因為行級X鎖是互斥的但表級意向鎖不互斥。現在回到開頭的場景事務B想給表加X鎖。它只需要檢查兩件事1. 當前是否有其他事務持有該表的X鎖2. 當前是否有其他事務持有該表的IS或IX鎖只要發現任何一個事務B的加鎖請求就會被阻塞。這個檢查是瞬間完成的無需遍歷每一行。行鎖升級為表鎖的常見誤區網上常說“InnoDB的行鎖會在某些情況下升級為表鎖”這其實是一個容易誤導的說法。更準確的情況是索引失效導致鎖升級如果UPDATE或DELETE語句的WHERE條件無法使用索引InnoDB將無法通過索引精確定位到需要鎖定的行。為了保證數據一致性它可能會退而求其次鎖定所有被掃描過的行甚至鎖定整個索引或整個表。這本質上不是“升級”而是因為無法使用行鎖而被迫使用更粗粒度的鎖。這也是為什么我們必須為高頻查詢和更新的字段建立合適索引的重要原因之一。-- 假設phone字段沒有索引 UPDATE users SET status 1 WHERE phone 13800138000; -- 這條語句會進行全表掃描對掃描到的每一行可能是全表都嘗試加X鎖效果類似于鎖表。顯式的表鎖語句使用了LOCK TABLES ... READ/WRITE這是應用層主動要求的表鎖與引擎無關。所以在InnoDB中表鎖意向鎖、自增鎖等和行鎖是協同工作的共同構建起一套高效的并發控制體系。理解意向鎖是理解InnoDB鎖機制如何平衡精度與效率的關鍵。4. 超越單行間隙鎖、臨鍵鎖與幻讀的防治在“可重復讀Repeatable Read, RR”隔離級別下InnoDB引入了兩種更復雜的鎖用于解決“幻讀Phantom Read”問題間隙鎖Gap Lock和臨鍵鎖Next-Key Lock。什么是幻讀在一個事務內兩次執行相同的查詢第二次查詢看到了第一次查詢時未出現的“幻影行”。注意幻讀強調的是“看到了新插入的行”。在“讀已提交Read Committed, RC”級別下幻讀是可能發生的。間隙鎖Gap Lock間隙鎖鎖定的不是一條具體的記錄而是索引記錄之間的“間隙”。例如表中存在id為5和10的記錄那么間隙鎖可以鎖定區間(5, 10)。它的存在是為了防止其他事務在這個間隙中插入新的記錄。間隙鎖是共享的。多個事務可以在同一個間隙上持有間隙鎖。它的唯一目的就是阻止插入。間隙鎖只存在于RR隔離級別或以上在RC級別下不存在。臨鍵鎖Next-Key Lock臨鍵鎖是行鎖Record Lock 間隙鎖Gap Lock的組合。它既鎖定了記錄本身也鎖定了該記錄之前的間隙。假設索引中有值10, 11, 13, 20。那么臨鍵鎖可能鎖定的區間是(-∞, 10], (10, 11], (11, 13], (13, 20], (20, ∞)。它是一個“左開右閉”的區間。當InnoDB掃描索引并準備加鎖時默認使用的就是臨鍵鎖。這是RR級別下防止幻讀的主要手段。實戰案例分析范圍查詢如何加鎖假設表t有一個主鍵索引id現有數據1, 5, 10, 15。-- 事務A (RR隔離級別) START TRANSACTION; SELECT * FROM t WHERE id 10 AND id 15 FOR UPDATE;這條語句會查詢到id10這一條記錄。那么它到底鎖定了什么找到id10的記錄加上行鎖X鎖。為了阻止幻讀InnoDB還會加上間隙鎖。對于id 10它會鎖定[10, ∞)這個區間嗎不完全是。因為條件是id 15所以它找到的下一個索引鍵值是15。因此InnoDB實際加的是臨鍵鎖鎖定的范圍是[10, 15)。具體來說對id10的記錄加行鎖。對間隙(10, 15)加間隙鎖。 所以鎖定的區間是(5, 15]臨鍵鎖區間是左開右閉但這里id10是右閉端點整體效果是鎖住了10以及10到15之間的空隙。此時如果事務B嘗試執行以下操作-- 事務B INSERT INTO t (id) VALUES (12); -- 被阻塞因為12在(10, 15)間隙內 UPDATE t SET ... WHERE id 15; -- 成功因為15不在鎖定的臨鍵鎖范圍內右邊界是開區間。但如果是SELECT ... FOR UPDATE id15也可能被阻塞取決于具體實現和是否存在其他鎖。 INSERT INTO t (id) VALUES (8); -- 成功8在(5,10)間隙未被鎖定。如何觀察和驗證鎖信息可以通過performance_schema庫中的data_locks和data_lock_waits表MySQL 8.0來查看當前的鎖等待情況。對于更早的版本可以使用SHOW ENGINE INNODB STATUS命令在輸出的TRANSACTIONS部分查看鎖信息。這對于排查死鎖和鎖超時問題至關重要。理解間隙鎖和臨鍵鎖是掌握RR隔離級別下InnoDB行為的關鍵。它們雖然增強了數據一致性但也帶來了更復雜的鎖沖突可能性是許多死鎖場景的“元兇”。在設計索引和編寫SQL時必須考慮這些鎖的影響范圍。5. 鎖的沖突與化解死鎖的成因、排查與規避策略死鎖是并發系統中經典且棘手的問題。在MySQL中當兩個或更多事務相互等待對方釋放鎖并且都無法繼續推進時就形成了死鎖。InnoDB引擎內置了死鎖檢測機制一旦發現死鎖會立即回滾其中一個代價最小的事務通常以修改的行數為參考讓其他事務得以繼續。一個典型的死鎖場景假設有賬戶表accounts有兩個事務同時操作-- 事務1 START TRANSACTION; UPDATE accounts SET balance balance - 100 WHERE id 1; -- 持有id1的X鎖 UPDATE accounts SET balance balance 100 WHERE id 2; -- 嘗試獲取id2的X鎖 -- 事務2 START TRANSACTION; UPDATE accounts SET balance balance - 50 WHERE id 2; -- 持有id2的X鎖 UPDATE accounts SET balance balance 50 WHERE id 1; -- 嘗試獲取id1的X鎖執行時序如下事務1鎖定id1。事務2鎖定id2。事務1嘗試鎖定id2發現被事務2持有于是等待。事務2嘗試鎖定id1發現被事務1持有于是等待。 至此事務1等待事務2事務2等待事務1形成循環等待死鎖產生。InnoDB檢測到后會回滾其中一個事務比如事務2并向客戶端返回1213 - Deadlock found when trying to get lock; try restarting transaction錯誤。事務1隨后可以成功執行。死鎖的排查工具SHOW ENGINE INNODB STATUS這是最常用的工具。在輸出中查找LATEST DETECTED DEADLOCK部分它會詳細記錄最后一次死鎖發生的時間、涉及的事務、正在執行的SQL語句、以及每個事務持有和等待的鎖。這是分析死鎖根因的第一手資料。performance_schema在MySQL 5.7 / 8.0中可以啟用performance_schema的相關消費者consumers通過查詢events_transactions_current,data_locks,data_lock_waits等表來實時監控鎖和事務狀態進行更深入的分析。規避死鎖的實戰策略死鎖無法完全避免但可以通過良好的設計和編碼習慣大幅降低其發生概率和影響保持事務小巧且快速事務越大持有鎖的時間越長與其他事務沖突的概率就越高。盡快提交或回滾事務。約定一致的訪問順序這是最重要、最有效的原則。在業務邏輯中如果存在多個需要更新的對象如表、行所有事務都按照相同的順序去訪問它們。例如總是先更新id小的賬戶再更新id大的賬戶。在上面的例子中如果兩個事務都按id1 - id2的順序更新就不會發生死鎖。為查詢創建合適的索引避免因為索引失效導致鎖范圍擴大從行鎖升級為類似表鎖的行為增加沖突面。使用EXPLAIN檢查SQL的執行計劃。降低隔離級別如果業務允許將隔離級別從RR降為RC。RC級別下沒有間隙鎖可以消除大量因間隙鎖導致的死鎖。但需評估幻讀風險。使用樂觀鎖對于沖突不那么激烈的場景可以考慮使用樂觀鎖。在表中增加一個版本號version字段。更新時將version作為條件。-- 樂觀鎖更新示例 UPDATE products SET stock stock - 1, version version 1 WHERE id 100 AND version 5;如果更新影響行數為0說明版本號已被其他事務修改本次更新失敗需要在應用層重試或提示用戶。設置合理的鎖等待超時通過innodb_lock_wait_timeout參數默認50秒設置鎖等待超時時間。對于不重要的后臺任務可以設置一個較短的超時時間避免長時間阻塞。但這只是緩解不是解決。重試機制在應用層捕獲死鎖異常如1213錯誤并進行有限次數的重試。這對于由短時鎖競爭引起的偶發死鎖非常有效。死鎖是系統并發度達到一定水平后的自然產物不必談之色變。關鍵在于建立有效的監控、分析和應對機制將其對業務的影響控制在可接受的范圍內。6. 隱形的守護者與性能殺手元數據鎖與自增鎖除了我們熟知的用于保護數據的鎖MySQL還有兩種至關重要的“后臺鎖”它們不直接參與業務數據的并發控制卻深刻影響著數據庫的可用性和性能元數據鎖Metadata Lock, MDL和自增鎖AUTO-INC Lock。元數據鎖MDL表結構的守護神文章開頭的事故罪魁禍首就是MDL鎖。MDL鎖是Server層實現的鎖用于保護表結構元數據的一致性防止在查詢或修改表數據的同時表結構被更改如ALTER TABLE,DROP TABLE。MDL鎖也分為不同級別MDL讀鎖在執行DML操作SELECT,INSERT,UPDATE,DELETE時自動獲取。多個事務可以同時持有同一表的MDL讀鎖。MDL寫鎖在執行DDL操作ALTER TABLE,DROP TABLE時自動獲取。MDL寫鎖是排他的。MDL鎖的阻塞與死鎖風險MDL鎖的引入帶來了一個經典的阻塞鏈問題一個長查詢或長事務SELECT * FROM big_table開始執行獲取了該表的MDL讀鎖。此時另一個線程執行ALTER TABLE big_table ADD COLUMN ...它需要獲取MDL寫鎖。由于MDL讀鎖與寫鎖互斥這個DDL操作被阻塞進入等待隊列。后續所有新的、針對big_table的查詢需要MDL讀鎖都會被這個等待的MDL寫鎖阻塞因為MDL鎖的調度是“寫鎖優先”在寫鎖等待期間新的讀鎖申請也會被堵在后面。 這就導致了“一榮俱榮一損俱損”的雪崩效應一個慢查詢或未提交的事務可以阻塞整個表的所有后續訪問包括查詢。如何應對MDL鎖問題監控與發現使用SHOW PROCESSLIST命令查看線程狀態關注Waiting for table metadata lock。在performance_schema中可以通過metadata_locks表查看MDL鎖的詳細信息。規范DDL操作在業務低峰期執行這是鐵律。使用pt-online-schema-change或gh-ost等在線改表工具這些工具通過創建影子表、同步數據、原子切換的方式避免了長時間持有MDL寫鎖對業務影響極小。對于核心表的結構變更應強制使用此類工具。設置超時在MySQL 5.7可以為ALTER TABLE設置LOCK和ALGORITHM選項有時能減少鎖持有時間。但最根本的還是用在線工具。避免長事務及時提交事務特別是使用了BEGIN或START TRANSACTION顯式開啟的事務。快速定位并Kill阻塞源通過information_schema.innodb_trx結合SHOW PROCESSLIST找到持有MDL讀鎖的長時間運行的事務或查詢并評估是否可以將其Kill掉。自增鎖AUTO-INC Lock序列號的秩序維持者當表中有AUTO_INCREMENT列時InnoDB使用一種特殊的表級鎖——自增鎖來保證為每一行新數據生成的自增主鍵值是唯一且連續的。自增鎖的行為模式由innodb_autoinc_lock_mode參數控制innodb_autoinc_lock_mode 0(“傳統”模式)每次執行INSERT語句時都會獲取一個特殊的表級AUTO-INC鎖并在語句結束后釋放。這保證了所有INSERT語句的自增ID是連續且可預測的但并發插入性能最差。innodb_autoinc_lock_mode 1(“連續”模式默認值)這是大多數情況下的最佳選擇。對于“簡單插入”能預先確定插入行數的語句如INSERT INTO t VALUES (1), (2), (3)它使用一個輕量級的互斥量來生成ID不需要持有AUTO-INC鎖到語句結束大大提升了并發性。對于“批量插入”如INSERT ... SELECT,LOAD DATA它仍然會使用AUTO-INC鎖以保證生成的ID對于當前語句是連續的。innodb_autoinc_lock_mode 2(“交錯”模式)所有INSERT語句都不使用AUTO-INC鎖完全依靠互斥量生成ID。這能提供最高的并發插入性能但會帶來兩個后果1) 同一語句內生成的自增ID可能不連續2) 在基于語句的復制SBR模式下可能導致主從不一致。因此只有在使用行復制RBR或混合復制MBR時才考慮使用此模式。實戰建議除非有非常特殊的、要求絕對連續ID且并發插入不高的場景否則保持默認的innodb_autoinc_lock_mode 1即可。它很好地平衡了性能、一致性和并發性。理解MDL鎖和自增鎖能幫助我們在進行表結構變更和高并發插入時提前預判風險制定更穩妥的方案避免它們從“守護者”變成“性能殺手”。7. 鎖的實戰觀測與性能優化思路理論最終要服務于實踐。我們如何直觀地看到數據庫中的鎖又該如何基于對鎖的理解來優化系統性能這是本章要解決的核心問題。鎖信息觀測實戰SHOW ENGINE INNODB STATUS(經典方法) 執行該命令后在輸出結果中重點關注TRANSACTIONS和LATEST DETECTED DEADLOCK如果有部分。這里會顯示當前活躍事務、持有的鎖以及鎖等待信息。雖然信息是文本格式不如新系統表直觀但在所有版本中均可用。---TRANSACTION 1234567890, ACTIVE 10 sec starting index read mysql tables in use 1, locked 1 LOCK WAIT 2 lock struct(s), heap size 1136, 1 row lock(s) MySQL thread id 100, OS thread handle 0x7f123456, query id 200 localhost root updating UPDATE accounts SET balance balance - 100 WHERE id 2 ------- TRX HAS BEEN WAITING 5 SEC FOR THIS LOCK TO BE GRANTED: RECORD LOCKS space id 300 page no 3 n bits 72 index PRIMARY of table test.accounts trx id 1234567890 lock_mode X locks rec but not gap waiting Record lock, heap no 3 PHYSICAL RECORD: ...這段輸出告訴我們事務1234567890正在等待一個行鎖lock_mode X鎖的模式是locks rec but not gap記錄鎖非間隙鎖鎖在accounts表的主鍵索引上。information_schema與performance_schema(推薦MySQL 5.7/8.0)information_schema.innodb_locks/innodb_lock_waits(在8.0中已被移除遷移至performance_schema)可以查看當前的鎖和鎖等待關系。performance_schema.data_locks(MySQL 8.0)記錄了所有當前持有的鎖包括行鎖、表鎖、意向鎖等的詳細信息如鎖類型、模式、所屬對象等。performance_schema.data_lock_waits(MySQL 8.0)記錄了當前的鎖等待關系明確指出哪個事務在等待哪個事務持有的鎖。一個常用的排查鎖阻塞的查詢-- MySQL 8.0 SELECT r.trx_id AS waiting_trx_id, r.trx_mysql_thread_id AS waiting_thread, r.trx_query AS waiting_query, b.trx_id AS blocking_trx_id, b.trx_mysql_thread_id AS blocking_thread, b.trx_query AS blocking_query FROM performance_schema.data_lock_waits w INNER JOIN information_schema.innodb_trx b ON b.trx_id w.blocking_engine_transaction_id INNER JOIN information_schema.innodb_trx r ON r.trx_id w.requesting_engine_transaction_id;這個查詢能清晰地展示出“誰被誰阻塞了”是診斷鎖等待問題的利器。基于鎖機制的SQL編寫與索引設計優化對鎖的理解直接影響我們編寫SQL和設計索引的方式盡量使用主鍵或唯一索引進行更新/刪除這能確保InnoDB使用行鎖將鎖的粒度控制在最小范圍。避免全表掃描導致的鎖升級。避免大范圍更新特別是無索引的更新UPDATE table SET status 0 WHERE status 1如果status字段沒有索引會鎖住大量甚至全部記錄極易引發長時間鎖等待和死鎖。務必為WHERE條件中的字段添加索引。謹慎使用SELECT ... FOR UPDATE明確你真的需要“當前讀”并鎖定數據。如果只是要保證讀取一致性RR級別下的普通SELECT快照讀通常就夠了它不會加鎖并發性能更高。只在需要基于查詢結果立即進行更新且要防止其他事務修改時才使用FOR UPDATE。將大事務拆分為小事務這是黃金法則。一個更新10萬行的事務持有鎖的時間可能長達幾分鐘。拆分成每次更新1000行并在每個批次后提交能顯著減少鎖的持有時間和沖突概率。注意間隙鎖的影響范圍在RR級別下范圍查詢BETWEEN,,和FOR UPDATE會加間隙鎖。在設計業務邏輯和索引時要考慮這個特性。有時使用SELECT ... FOR UPDATE SKIP LOCKEDMySQL 8.0可以跳過已被鎖定的行實現簡單的無鎖隊列提高并發處理能力。監控innodb_row_lock_*狀態變量通過SHOW STATUS LIKE innodb_row_lock%;可以查看行鎖的競爭情況。innodb_row_lock_current_waits當前正在等待行鎖的數量。innodb_row_lock_time系統啟動以來行鎖定的總時間毫秒。innodb_row_lock_time_avg每次行鎖等待的平均時間。innodb_row_lock_time_max行鎖等待的最長時間。innodb_row_lock_waits系統啟動以來行鎖等待發生的總次數。 如果innodb_row_lock_waits和innodb_row_lock_time_avg持續很高說明行鎖競爭激烈需要優化。鎖是數據庫并發控制的精髓也是性能調優的深水區。從被動地解決鎖超時、死鎖告警到主動地通過設計規避鎖沖突是每個后端開發者成長的必經之路。掌握觀測工具理解鎖的行為模式才能讓我們在構建高并發、高可用的數據服務時真正做到心中有數游刃有余。