目錄
前言
MySQL索引
一、概述
二、索引分類
(一)按功能特性分類
(二)按存儲方式分類
(三)按數據結構分類
(四)按索引字段數量分類
三、索引的優缺點
(一)優點
(二)缺點
四、普通索引的使用
(一)使用規則
(二)創建索引
建表時創建
建表后添加
(三)?查看索引
(四)刪除索引
(五)驗證查詢是否使用索引
(六)索引使用建議
五、其它索引的使用
(一)唯一索引
(二)復合索引
(三)主鍵索引
慢SQL分析與優化
一、定義
二、常見原因
三、影響
四、如何發現慢SQL
(一)開啟慢查詢日志
(二)重啟MySQL服務
(三)查看慢日志文件路徑
(四)分析慢查詢日志
五、如何優化慢SQL
(一)優化查詢語句
(二)優化數據庫結構
(三)調整數據庫配置
(四) 硬件和系統優化
(五)使用性能分析工具
六、優化示例
(一)對索引字段使用函數導致索引失效
優化前
問題分析
優化后
優化說明
驗證方法
(二)模糊查詢導致索引失效
優化前
問題分析
優化后
優化說明
驗證方法
(三)隱式類型轉換導致索引失效
優化前
問題分析
優化后
優化說明
驗證方法
(四)聯合索引未遵循最左前綴原則
優化前
問題分析
優化后
優化說明
驗證方法
優化案例
一、案例背景
二、優化過程
(一)問題定位
開啟慢查詢日志
使用?EXPLAIN?分析執行計劃
(二)優化方案
索引優化
SQL 改寫
歸檔歷史數據
(三)最終優化組合
三、優化總結
鎖機制與并發優化
一、事務與鎖機制
(一)SQL 語句類型
(二)事務
(三)ACID 特性
二、鎖機制
(一)全局鎖
介紹
作用
示例
解釋
(二)表級鎖
介紹
作用
示例
解釋
(三)行級鎖
介紹
作用
示例
解釋
(四)元數據鎖
介紹
作用
示例
解釋
三、鎖機制在MySQL優化中的價值
前言
- 前面幾篇博客已經詳細介紹了MySQL的基本使用(查詢、建表、備份、主從同步、讀寫分離、分庫分表),感興趣的可以點擊主頁查看。
MySQL索引
一、概述
- 索引是數據庫中用于提高查詢效率的數據結構,類似于書籍的目錄。
- 通過索引,數據庫可以快速定位到目標數據,避免全表掃描,從而顯著提升查詢性能。
二、索引分類
(一)按功能特性分類
索引類型 | 功能描述 |
---|---|
普通索引 | 最基本的索引類型,僅用于加速查詢,無唯一性約束。 |
唯一索引 | 保證索引列的值唯一,允許存在 NULL 值。 |
主鍵索引 | 特殊的唯一索引,不允許 NULL 值,每個表只能有一個主鍵索引。 |
注:本文主要介紹普通索引的使用。
(二)按存儲方式分類
索引類型 | 功能描述 |
---|---|
聚簇索引 | 數據行與索引存儲在一起,索引順序即數據物理存儲順序。 InnoDB 中主鍵默認為聚簇索引。 |
非聚簇索引 | 索引與數據分開存儲,索引中僅保存指向數據行的指針。 |
注:InnoDB 存儲引擎中,聚簇索引即為主鍵索引,非聚簇索引也稱為二級索引。
(三)按數據結構分類
索引類型 | 功能描述 |
---|---|
B-Tree 索引 | MySQL 默認索引類型,支持范圍查詢和排序操作,適用于大多數場景。 |
哈希索引 | 基于哈希表實現,僅支持等值查詢,不支持范圍查詢,Memory 引擎支持。 |
全文索引 | 用于對大文本字段進行關鍵詞搜索,支持自然語言查詢,InnoDB 和 MyISAM 支持。 |
空間索引 | 用于地理空間數據類型(如 POINT、POLYGON),支持空間查詢,MyISAM 支持。 |
(四)按索引字段數量分類
索引類型 | 功能描述 |
---|---|
單列索引 | 僅作用于單個字段,適用于簡單查詢條件。 |
復合索引(組合索引) | 作用于多個字段,遵循“最左前綴”原則,適用于多條件查詢。 |
最左前綴原則:復合索引中,查詢條件必須從索引的最左側字段開始匹配,才能有效使用索引。
三、索引的優缺點
(一)優點
列舉 | 描述 |
---|---|
提高查詢速度 | 通過索引結構快速定位數據,顯著減少查詢時間。 |
減少數據掃描量 | 避免全表掃描,降低服務器負載。 |
避免排序和臨時表 | 索引本身有序,支持?ORDER BY ?和?GROUP BY ,減少額外排序開銷。 |
優化磁盤 I/O | 將隨機 I/O 轉換為順序 I/O,提高磁盤訪問效率。 |
(二)缺點
列舉 | 描述 |
---|---|
降低更新效率 | 插入、更新、刪除操作需同步維護索引,增加寫入開銷。 |
占用額外空間 | 索引文件占用磁盤空間,復合索引或大表索引空間開銷顯著。 |
對低選擇性字段效果差 | 字段重復值多時,索引過濾效果不明顯,甚至可能不如全表掃描。 |
小表查詢收益低 | 數據量較小時,全表掃描成本更低,索引優勢不明顯。 |
四、普通索引的使用
(一)使用規則
規則 | 說明 |
---|---|
索引數量 | 一個表中可以創建多個普通索引。 |
字段類型 | 支持為任意數據類型的字段創建索引。 |
值約束 | 字段值允許重復,也允許為 NULL。 |
使用場景 | 通常在為?WHERE ?子句提供條件的字段上創建索引,以提高查詢效率。 |
索引標志 | 普通索引在表結構中標志為?MUL (Multiple Key)。 |
(二)創建索引
建表時創建
CREATE TABLE 庫.表(
字段列表,
INDEX(字段名),
INDEX(字段名)
);
# 創建表時添加索引
CREATE TABLE users (id INT PRIMARY KEY,name VARCHAR(50),email VARCHAR(100),INDEX idx_name (name)
);
建表后添加
CREATE INDEX idx_name ON test.users(name)
(三)?查看索引
DESC 庫名.表名; #查看表結構(簡略)
SHOW INDEX FROM 表名\G # 查看索引詳細信息#示例
DESC test.users;
SHOW INDEX FROM users\G
(四)刪除索引
DROP INDEX 索引名 ON 庫.表;#示例
DROP INDEX idx_name ON test.users;
(五)驗證查詢是否使用索引
EXPLAIN SELECT 查詢語句;#示例
EXPLAIN SELECT * FROM test.users WHERE name = 'test';
- 關鍵字段說明:
type
:訪問類型,ref
、range
、const
表示使用索引,ALL
表示全表掃描。key
:實際使用的索引名稱。rows
:預估掃描行數,越少越好。Extra
:附加信息,如Using index
表示覆蓋索引,Using filesort
表示需額外排序。
(六)索引使用建議
- 選擇性高的字段優先建立索引(如用戶ID、訂單號)。
- 避免對低基數字段建索引(如性別、狀態字段)。
- 聯合索引字段順序應遵循查詢條件順序。
- 避免在索引列上使用函數或表達式,會導致索引失效。
- 定期分析和優化索引,刪除冗余或無效索引。
五、其它索引的使用
- 僅介紹創建示例其它部分類似普通索引的使用
(一)唯一索引
CREATE UNIQUE INDEX email_unique ON users(email);
(二)復合索引
CREATE INDEX name_age ON users(name, age);
(三)主鍵索引
ALTER TABLE users ADD PRIMARY KEY (id);
慢SQL分析與優化
一、定義
- 慢SQL是指執行時間超過預設閾值的SQL語句,這類查詢通常執行效率低下,可能導致數據庫響應變慢、資源占用過高,進而影響整個系統的穩定性和用戶體驗。
- 在MySQL中,默認將執行時間超過10秒的SQL定義為慢SQL。
二、常見原因
原因 | 說明 |
---|---|
查詢未使用索引或索引失效 | 查詢未利用索引或索引因某些原因失效,導致全表掃描 |
查詢返回數據量過大 | 查詢結果集過大,增加網絡傳輸和內存消耗 |
SQL語句寫法不合理 | 如使用了不合理的JOIN、子查詢等,導致查詢效率低下 |
數據庫表結構設計不合理 | 表結構設計不當,如字段類型選擇不當、表關聯關系復雜等 |
數據庫參數配置不當 | 數據庫參數設置不合理,如緩沖區大小、連接數等 |
服務器硬件資源不足 | CPU、內存、磁盤IO等硬件資源不足,影響查詢性能 |
三、影響
影響 | 說明 |
---|---|
系統響應變慢,用戶體驗下降 | 查詢響應時間延長,用戶等待時間增加 |
數據庫連接池耗盡,導致系統崩潰 | 慢SQL占用連接時間過長,導致連接池資源耗盡 |
增加數據庫服務器負載,影響其他業務 | 數據庫服務器資源被大量占用,影響其他業務的正常運行 |
四、如何發現慢SQL
(一)開啟慢查詢日志
通過修改MySQL配置文件my.cnf
或使用命令行參數,開啟慢查詢日志功能,記錄執行時間超過設定閾值的SQL語句。
- 修改配置文件
my.cnf
[mysqld] slow_query_log = 1 # 開啟慢查詢日志 long_query_time = 1 # 設置閾值(單位:秒) log_queries_not_using_indexes = 1 # 記錄未使用索引的查詢
- 命令行動態設置
#登錄MySQL后執行SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = 'ON';
(二)重啟MySQL服務
systemctl restart mysqld
(三)查看慢日志文件路徑
SHOW VARIABLES LIKE 'slow_query_log_file';
(四)分析慢查詢日志
- 使用
mysqldumpslow
工具對慢查詢日志進行分析,找出執行時間最長的SQL語句。mysqldumpslow -s t -t 10 /var/lib/mysql/mysql-slow.log # 按時間排序,顯示前10條慢SQL # -s t 按查詢時間排序 # -t 10 顯示前10條記錄
五、如何優化慢SQL
(一)優化查詢語句
- 使用合適的索引:確保查詢條件中的字段有索引,并避免索引失效。
- 避免全表掃描:盡量使用索引字段進行查詢,減少掃描的數據量。
- 優化JOIN操作:確保JOIN字段有索引,避免使用復雜的嵌套JOIN。
- 限制返回數據量:使用
LIMIT
限制返回的記錄數,避免返回過多數據。
(二)優化數據庫結構
- 合理設計表結構:避免冗余字段,合理劃分表結構。
- 使用合適的數據類型:選擇合適的數據類型,減少存儲空間和I/O開銷。
- 歸檔歷史數據:定期清理或歸檔舊數據,減少表的數據量。
(三)調整數據庫配置
- 調整緩沖區大小:如
innodb_buffer_pool_size
、query_cache_size
等。 - 優化連接數:根據系統負載合理設置最大連接數
max_connections
。 - 啟用查詢緩存:對于讀多寫少的應用,可以開啟查詢緩存。
(四) 硬件和系統優化
- 升級硬件資源:增加CPU、內存、使用SSD等,提升系統性能。
- 優化操作系統參數:如文件描述符限制、內核參數等。
(五)使用性能分析工具
- EXPLAIN:分析SQL的執行計劃,查看是否使用了索引。
- SHOW PROFILE:分析SQL執行過程中的資源消耗。
- Performance Schema:MySQL的性能分析工具,可以詳細監控SQL執行情況。
六、優化示例
(一)對索引字段使用函數導致索引失效
優化前
SELECT * FROM orders WHERE DATE(create_time) = '2025-07-01';
問題分析
- 對
create_time
字段使用了DATE()
函數,導致 MySQL 無法使用該字段上的索引。 - 結果是全表掃描,查詢性能差,尤其是在數據量大的情況下。
優化后
SELECT * FROM orders
WHERE create_time >= '2025-07-01' AND create_time < '2025-07-02';
優化說明
- 通過將函數操作轉換為范圍查詢,避免對索引字段進行計算。
- 使 MySQL 能夠利用
create_time
字段上的索引,顯著減少掃描行數。
驗證方法
EXPLAIN SELECT * FROM orders WHERE create_time >= '2025-07-01' AND create_time < '2025-07-02';
- 查看
type
是否為range
,key
是否為create_time
的索引名。 - 確認
rows
字段值大幅減少,表明索引生效。
(二)模糊查詢導致索引失效
優化前
SELECT * FROM users WHERE name LIKE '%青竹%';
問題分析
- 使用了前模糊查詢(
%青竹%
),導致 MySQL 無法使用name
字段上的索引。 - 結果是全表掃描,查詢效率低。
優化后
SELECT * FROM users WHERE name LIKE '青竹%';
優化說明
- 將前模糊查詢改為后模糊查詢(青竹
%
),使 MySQL 能夠利用name
字段上的索引。 - 適用于以特定前綴開頭的搜索場景。
驗證方法
EXPLAIN SELECT * FROM users WHERE name LIKE '青竹%';
- 查看
type
是否為range
,key
是否為name
的索引名。 - 確認
rows
字段值減少,表明索引生效。
(三)隱式類型轉換導致索引失效
優化前
SELECT * FROM users WHERE phonenumber = 15212341234;
問題分析
phonenumber
字段為字符串類型,但查詢時使用了數字類型,導致 MySQL 進行隱式類型轉換。- 結果是索引失效,全表掃描。
優化后
SELECT * FROM users WHERE phonenumber = 15212341234;
優化說明
- 將查詢條件中的數字類型改為字符串類型,避免隱式類型轉換。
- 使 MySQL 能夠利用
phone
字段上的索引。
驗證方法
EXPLAIN SELECT * FROM users WHERE phonenumber = 15212341234;
- 查看
type
是否為ref
,key
是否為phonenumber
的索引名。 - 確認
rows
字段值減少,表明索引生效。
(四)聯合索引未遵循最左前綴原則
優化前
SELECT * FROM users WHERE age = 24;
問題分析
- 存在聯合索引
name_age(name, age)
,但查詢條件中未包含name
字段。 - 結果是索引失效,全表掃描。
優化后
SELECT * FROM users WHERE name = '青竹' AND age = 24;
優化說明
- 在查詢條件中加入
name
字段,遵循聯合索引的最左前綴原則。 - 使 MySQL 能夠利用
name_age
聯合索引。
驗證方法
EXPLAIN SELECT * FROM users WHERE name = '青竹' AND age = 24;
- 查看
type
是否為ref
,key
是否為name_age
。 - 確認
rows
字段值減少,表明索引生效。
優化案例
一、案例背景
- 某電商平臺隨著業務的快速發展,訂單量不斷攀升。
- 在日常運營中,訂單統計報表查詢功能頻繁被使用,用于生成各類銷售數據報表,為運營決策提供數據支持。
- 然而,隨著訂單數據量的急劇增長,當訂單表中的數據量達到 600 萬條時,報表查詢的性能問題逐漸凸顯。
- 具體表現為,以下用于統計用戶訂單總額和訂單數量的 SQL 查詢語句,執行時間超過 10 秒:
SELECT customer_id,COUNT(*) AS order_count,SUM(order_amount) AS total_order_amount FROM orders WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'AND status IN (1, 2, 3) GROUP BY customer_id ORDER BY total_order_amount DESC LIMIT 100;
二、優化過程
(一)問題定位
開啟慢查詢日志
- 通過修改 MySQL 配置文件
my.cnf
或使用命令行參數,開啟慢查詢日志功能,記錄執行時間超過設定閾值的 SQL 語句。 - 修改配置文件
my.cnf
[mysqld] slow_query_log = 1 # 開啟慢查詢日志 slow_query_log_file = /var/log/mysql/slow.log # 指定日志文件路徑 long_query_time = 1 # 設置閾值(單位:秒) log_queries_not_using_indexes = 1 # 記錄未使用索引的查詢
- 命令行動態設置
#登錄 MySQL 后執行SET GLOBAL slow_query_log = 'ON'; SET GLOBAL long_query_time = 1; SET GLOBAL log_queries_not_using_indexes = 'ON';
使用?EXPLAIN
?分析執行計劃
EXPLAIN
SELECT customer_id, COUNT(*), SUM(order_amount)
FROM orders
WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'AND status IN (1, 2, 3)
GROUP BY customer_id
ORDER BY total_order_amount DESC
LIMIT 100;
- 執行計劃關鍵指標:
id select_type table type possible_keys key key_len ref rows filtered Extra 1 SIMPLE orders ALL NULL NULL NULL NULL 6000000 10.00 Using where; Using filesort
- ???????問題點:???????
-
type: ALL
:表示全表掃描,未使用索引。 -
Using filesort
:表示需要額外排序,增加 CPU 和 I/O 開銷。 -
Using temporary
:表示使用了臨時表,常見于GROUP BY
操作。
-
(二)優化方案
索引優化
- 創建聯合索引,覆蓋
WHERE
條件和GROUP BY
字段:ALTER TABLE orders ADD INDEX idx_status_createtime_customerid (status, create_time, customer_id);
- 優化后執行計劃
id | select_type | table | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
---|---|---|---|---|---|---|---|---|---|---|
1 | SIMPLE | orders | range | idx_status_createtime_customerid | idx_status_createtime_customerid | 6 | NULL | 150000 | 100.00 | Using where; Using index; Using filesort |
- 優化效果:
- 掃描行數從 600 萬減少到 15 萬。
- 使用覆蓋索引(
Using index
),避免回表。 - 執行時間降至約 1.5 秒。
SQL 改寫
- 原始寫法在
GROUP BY
后需要對大量數據進行排序,性能較差。 - 優化思路是先縮小數據集再排序:
SELECT customer_id, order_count, total_order_amount FROM (SELECT customer_id,COUNT(*) AS order_count,SUM(order_amount) AS total_order_amount FROM orders WHERE create_time BETWEEN '2024-01-01' AND '2024-12-31'AND status IN (1, 2, 3)GROUP BY customer_id ) AS tmp ORDER BY total_order_amount DESC LIMIT 100;
- 優化效果:
- 避免對大結果集排序。
- 執行時間進一步降至約 0.9 秒。
歸檔歷史數據
將超過 2 年的訂單數據遷移到歷史表,減少主表數據量:
-- 創建歸檔表
CREATE TABLE orders_archive LIKE orders;-- 遷移數據
INSERT INTO orders_archive
SELECT * FROM orders
WHERE create_time < '2023-01-01';-- 刪除原表過期數據
DELETE FROM orders
WHERE create_time < '2023-01-01';
- 優化效果:
- 數據量從 600 萬減少到 400 萬。
- 查詢時間穩定在 0.6 秒以內。
(三)最終優化組合
- 聯合索引:
idx_status_createtime_customerid
- SQL 改寫:先聚合后排序
- 數據歸檔:定期清理歷史數據
- 參數調優:調整
my.cnf
中的相關參數[mysqld] tmp_table_size = 256M #定義MySQL內部臨時表(MEMORY 表)的最大允許大小 #臨時表的大小超過此值,MySQL會將其轉儲到磁盤,這會顯著降低性能 #單位為字節,建議設為服務器內存的 1%-2%max_heap_table_size = 256M #限制用戶顯式創建的 MEMORY表的最大大小(通過 CREATE TABLE ... ENGINE=MEMORY)sort_buffer_size = 8M #定義每個連接用于排序操作(如 ORDER BY、DISTINCT、GROUP BY)的緩沖區大小 #排序操作會將數據加載到緩沖區,如果緩沖區不足,MySQL 會使用磁盤臨時文件(filesort),導致性能下降
三、優化總結
優化手段 | 原理說明 | 適用場景 |
---|---|---|
聯合索引 | 覆蓋?WHERE ?和?GROUP BY ?字段,減少回表和排序開銷 | 多條件過濾 + 分組統計查詢 |
SQL 改寫 | 減少中間結果集大小,避免全量排序 | 含?LIMIT ?的分頁或 TopN 查詢 |
數據歸檔 | 降低單表數據量,減少索引樹高度 | 時間序列數據的大表 |
參數調優 | 增加內存分配,避免磁盤臨時表 | 復雜排序或分組操作 |
鎖機制與并發優化
一、事務與鎖機制
(一)SQL 語句類型
分類 | 全稱 | 關鍵字 | 作用 |
---|---|---|---|
DDL | Data Definition Language | CREATE ?/?ALTER ?/?DROP | 定義或修改數據庫、表、索引、視圖等結構 |
DML | Data Manipulation Language | INSERT ?/?UPDATE ?/?DELETE | 對表中數據進行增、刪、改 |
DQL | Data Query Language | SELECT ?/?SHOW | 查詢數據 |
DCL | Data Control Language | GRANT ?/?REVOKE | 權限控制 |
TCL | Transaction Control Language | BEGIN ?/?COMMIT ?/?ROLLBACK ?/?SAVEPOINT | 事務控制 |
(二)事務
-
作用域:僅針對 DML(
INSERT
、UPDATE
、DELETE
)。 -
定義:一組 DML 語句要么全部成功,要么全部失敗,保證數據準確性。
(三)ACID 特性
特性 | 描述 |
---|---|
原子性 Atomicity | 事務內所有 DML 操作要么全部成功,要么全部回滾。 |
一致性 Consistency | 事務完成后,數據庫從一種一致狀態變為另一種一致狀態。 |
隔離性 Isolation | 并發事務互不干擾,隔離級別控制可見性。 |
持久性 Durability | 一旦提交,數據永久生效,即使系統崩潰。 |
二、鎖機制
鎖類別 | 粒度 | 類型 | 典型語句 | 事務關聯 |
---|---|---|---|---|
全局鎖 | 實例級 | 只讀 | FLUSH TABLES WITH READ LOCK | 一致性備份 |
表級鎖 | 表級 | 讀/寫 | LOCK TABLES t WRITE | 離線批處理 |
行級鎖 | 行級 | 共享/排他 | SELECT … FOR UPDATE | 并發 DML 事務 |
MDL | 表級(自動) | 元數據鎖 | 隱式 | 防止 DDL 與 DML 沖突 |
(一)全局鎖
介紹
- 全局鎖通過
FLUSH TABLES WITH READ LOCK
對整個 MySQL 實例加鎖,所有表瞬間變為只讀,任何寫事務或 DDL 都會被阻塞。
作用
- 用于需要“整庫一致性快照”的場景:全庫邏輯備份、主從切換、版本升級前的數據校驗。
示例
#加全局讀鎖
FLUSH TABLES WITH READ LOCK;#在另一個終端執行一致性備份
mysqldump -u root -ptest --single-transaction test > backup.sql#備份完成后立即解鎖
UNLOCK TABLES;
解釋
FLUSH TABLES WITH READ LOCK
會刷新所有表并加全局讀鎖;此時所有寫操作被阻塞。- 配合
mysqldump --single-transaction
可在 InnoDB 上實現“秒級”一致性備份,避免長時間鎖庫導致的業務寫入中斷。
(二)表級鎖
介紹
- 表級鎖通過
LOCK TABLES
顯式鎖定單張表,粒度比全局鎖小,會話級顯式鎖。
作用
- 適用于離線批量導入、報表生成、表維護(OPTIMIZE/ALTER 前)等需要獨占表的場景。
示例
#加表級寫鎖,確保 employees 表有主鍵索引,避免退化為全表鎖
LOCK TABLES employees WRITE;#批量更新(其他會話對該表的讀寫均被阻塞)
UPDATE employees SET status = 4 WHERE create_time < '2025-07-01';#解鎖
UNLOCK TABLES;
解釋
LOCK TABLES employees WRITE
獲得排他寫鎖,其他會話無法讀寫該表;若使用READ
則僅允許并發讀。- 會話退出或執行
UNLOCK TABLES
后鎖立即釋放;腳本中務必保證解鎖,避免鎖表雪崩。
(三)行級鎖
介紹
- InnoDB 存儲引擎支持的最細粒度鎖,僅鎖定索引記錄或記錄間隙,不影響其他行。
作用
- 在高并發 OLTP 場景下保證數據一致性,防止臟讀、幻讀、更新丟失。
示例
#開啟事務
START TRANSACTION;#對 id = 1 的行加排他鎖,WHERE 條件必須命中索引,否則行鎖會退化為表鎖
SELECT * FROM employees WHERE id = 1 FOR UPDATE;#執行更新
UPDATE employees SET name = '青竹' WHERE id = 1;#提交事務,鎖自動釋放
COMMIT;
解釋
FOR UPDATE
對主鍵 id = 1 的記錄加排他鎖,其他事務無法修改或加鎖該行。- 若 WHERE 條件無法走索引,會退化為表級鎖,因此務必確保有合適索引;長事務會阻塞后續操作。
(四)元數據鎖
介紹
- MDL 是 MySQL 5.5+ 自動維護的表級鎖,用于保護表結構一致性,防止 DDL 與 DML 并發沖突。
作用
- 任何 DML 語句都會隱式獲取 MDL 共享鎖;DDL 需要排他 MDL。
- 長事務未提交時,DDL 會被阻塞,出現 “Waiting for table metadata lock”。
示例
- 會話 A:
START TRANSACTION; SELECT * FROM employees WHERE id = 1 FOR UPDATE; #此時持有 employees 的 MDL 共享鎖
- 會話 B:
ALTER TABLE employees ADD COLUMN extra VARCHAR(20); #等待 MDL 排他鎖,被阻塞
- 查看鎖等待:
SELECT * FROM performance_schema.metadata_locks; SHOW PROCESSLIST;
解釋
- 會話 A 未提交,導致會話 B 的 ALTER 無法獲得排他 MDL。
- 解決:縮短事務、提交/回滾后再執行 DDL,或設置
lock_wait_timeout
避免無限等待。
三、鎖機制在MySQL優化中的價值
- 備份窗口最小化
- 全局鎖 +
--single-transaction
組合,將一致性備份時間從分鐘級降到秒級,降低業務抖動。
- 全局鎖 +
- 批量任務提速
- 表級寫鎖在低峰期一次性處理百萬級記錄,避免逐行鎖帶來的大量上下文切換。
- 并發沖突降級
- 行級鎖保證熱點數據高并發讀寫;表級鎖用于冷數據批量維護,兩者互補。
- DDL 平滑執行
- 通過監控 MDL 等待隊列,提前結束長事務,使 ALTER TABLE 在業務低峰期 10 秒內完成,避免高峰期鎖表雪崩。