以下通過 6 個真實案例展示如何使用 EXPLAIN
優化 SQL,每個案例包含問題 SQL、EXPLAIN 分析、優化方案和優化后效果對比:
案例 1:全表掃描優化 (type=ALL)
問題 SQL(用戶訂單查詢):
SELECT * FROM orders
WHERE user_id = 1005 AND create_date > '2023-01-01';
?? EXPLAIN 分析:
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| id | select_type | table | type | possible_keys | key | key_len | rows | Extra |
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
| 1 | SIMPLE | orders | ALL | NULL | NULL | NULL | 50萬 | Using where|
+----+-------------+--------+------+---------------+------+---------+------+--------+-------------+
診斷:
type=ALL
:全表掃描rows=500,000
:掃描 50 萬行key=NULL
:未使用索引
🔧 優化方案:
-- 添加復合索引
ALTER TABLE orders ADD INDEX idx_user_create (user_id, create_date);
? 優化后 EXPLAIN:
+----+-------------+--------+-------+---------------+------------------+---------+------+------+
| id | select_type | table | type | key | key_len | rows | Extra|
+----+-------------+--------+-------+---------------+---------+------+------+
| 1 | SIMPLE | orders | ref | idx_user_create | 8 | 15 | Using index |
+----+-------------+--------+-------+---------------+---------+------+------+
效果:
- 掃描行數 50萬 → 15 行
- 查詢時間 2.8秒 → 0.02秒
案例 2:文件排序優化 (Using filesort)
問題 SQL(最新商品查詢):
SELECT * FROM products
WHERE category = 'electronics'
ORDER BY create_time DESC
LIMIT 20;
?? EXPLAIN 分析:
+----+-------------+----------+------+---------------+------+---------+------+--------+-----------------------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+----------+------+---------------+------+-------------------------------------+
| 1 | SIMPLE | products | ref | idx_category | 2500 | Using index condition; Using filesort|
+----+-------------+----------+------+---------------+------+-------------------------------------+
診斷:
Using filesort
:額外文件排序- 雖然用了索引,但排序字段未包含
🔧 優化方案:
-- 創建包含排序字段的索引
ALTER TABLE products ADD INDEX idx_cat_time (category, create_time DESC);
? 優化后 EXPLAIN:
+----+-------------+----------+-------+----------------+---------+------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+----------+-------+----------------+---------+----------------+
| 1 | SIMPLE | products | ref | idx_cat_time | 2500 | Using where |
+----+-------------+----------+-------+----------------+---------+----------------+
效果:
- 移除
Using filesort
(索引已排好序) - 500ms 的文件排序操作 → 0ms
案例 3:子查詢優化 (DEPENDENT SUBQUERY)
問題 SQL(高消費用戶查詢):
SELECT * FROM users
WHERE id IN (SELECT user_id FROM orders WHERE amount > 1000
);
?? EXPLAIN 分析:
+----+--------------------+--------+----------------+---------------+-----------------+
| id | select_type | table | type | key | rows | Extra |
+----+--------------------+--------+----------------+---------------+-----------------+
| 1 | PRIMARY | users | ALL | NULL | 10萬 | Using where |
| 2 | DEPENDENT SUBQUERY | orders | index_subquery | idx_user | 30 | Using where |
+----+--------------------+--------+----------------+---------------+-----------------+
診斷:
DEPENDENT SUBQUERY
:外查詢每行都執行子查詢- 外層全表掃描 10萬行 × 子查詢 30行 = 實際掃描 300萬行
🔧 優化方案:
-- 改為 JOIN 寫法
SELECT u.*
FROM users u
JOIN (SELECT DISTINCT user_id FROM orders WHERE amount > 1000
) o ON u.id = o.user_id;
? 優化后 EXPLAIN:
+----+-------------+------------+--------+---------------+---------+------+-------+
| id | select_type | table | type | key | rows | Extra|
+----+-------------+------------+--------+---------------+---------+------+
| 1 | PRIMARY | <derived2> | ALL | NULL | 1500 | |
| 1 | PRIMARY | u | eq_ref | PRIMARY | 1 | |
| 2 | DERIVED | orders | range | idx_amount | 1500 | |
+----+-------------+------------+--------+---------------+---------+------+
效果:
- 執行時間 4.2秒 → 0.3秒
- 掃描總量 300萬行 → 1500 + 1500行
案例 4:索引覆蓋優化 (回表查詢)
問題 SQL(用戶統計):
SELECT username, email FROM users
WHERE register_time BETWEEN '2022-01-01' AND '2022-12-31';
?? EXPLAIN 分析:
+----+-------------+-------+-------+------------------+---------+------+-------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+-------+-------+------------------+---------+-------------+
| 1 | SIMPLE | users | range | idx_register_time| 15000 | Using where |
+----+-------------+-------+-------+------------------+---------+-------------+
診斷:
- Extra列信息僅顯示 Using where,?沒有出現 Using index?(重要!)
這表示:
雖然使用了索引idx_register_time定位數據(type=range證明索引生效)
但索引未覆蓋所有查詢字段,需回聚簇索引獲取完整行數據 - 雖然使用了索引,但需要回表查
username
,email
字段 - 潛在優化點:覆蓋索引
🔧 優化方案:
-- 創建包含所有查詢字段的覆蓋索引
ALTER TABLE users ADD INDEX idx_cover_register (register_time, username, email);
? 優化后 EXPLAIN:
+----+-------------+-------+-------+--------------------+---------+------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+-------+-------+--------------------+---------+------------------+
| 1 | SIMPLE | users | range | idx_cover_register | 15000 | Using where; Using index |
+----+-------------+-------+-------+--------------------+---------+------------------+
效果:
Using index
:避免回表操作- I/O 操作減少 60%
- 查詢時間 450ms → 120ms
案例 5:JOIN 優化 (錯誤的 JOIN 順序)
問題 SQL(訂單詳情查詢):
SELECT o.*, u.name
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.amount > 500 AND u.vip_level > 3;
?? EXPLAIN 分析:
+----+-------------+-------+------+---------------+---------+------+----------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+-------+------+---------------+---------+------------------------------+
| 1 | SIMPLE | u | ALL | idx_vip | 10000 | Using where |
| 1 | SIMPLE | o | ref | idx_user | 25 | Using where |
+----+-------------+-------+------+---------------+---------+------------------------------+
診斷:
- 先掃描 1萬VIP用戶,再關聯訂單
- 實際訂單篩選條件
amount>500
在關聯后執行
🔧 優化方案:
-- 重寫查詢調整 JOIN 順序
SELECT o.*, u.name
FROM (SELECT * FROM orders WHERE amount > 500 -- 先過濾大表
) o
JOIN users u ON o.user_id = u.id
WHERE u.vip_level > 3;
? 優化后 EXPLAIN:
+----+-------------+------------+--------+---------------+---------+------+--------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+------------+--------+---------------+---------+-------------+
| 1 | PRIMARY | <derived2> | ALL | NULL | 8000 | |
| 1 | PRIMARY | u | eq_ref | PRIMARY | 1 | Using where |
| 2 | DERIVED | orders | range | idx_amount | 8000 | Using where |
+----+-------------+------------+--------+---------------+---------+-------------+
效果:
- 減少驅動表數據量:1萬行 → 8000行
- 總掃描行數:1萬×25=25萬行 → 8000+8000行
- 執行時間:1.8秒 → 0.4秒
案例 6:分頁深度優化 (大偏移量分頁)
問題 SQL(第10000頁數據):
SELECT id, title FROM articles
ORDER BY create_time DESC
LIMIT 10000, 20; -- 跳過10000條
?? EXPLAIN 分析:
+----+-------------+----------+-------+---------------+---------+------+-------------+
| id | select_type | table | type | key | rows | Extra |
+----+-------------+----------+-------+---------------+---------+-----------------------+
| 1 | SIMPLE | articles | index | idx_create | 10020 | Using index |
+----+-------------+----------+-------+---------------+---------+-----------------------+
診斷:
rows=10020
:實際讀取 10020 行(即使最終只返回20條)- 深度分頁性能災難
🔧 優化方案:
-- 基于游標的優化寫法
SELECT id, title
FROM articles
WHERE create_time < '2023-06-01' -- 傳入上一頁的最后時間
ORDER BY create_time DESC
LIMIT 20;
? 優化效果對比:
方案 | 掃描行數 | 執行時間 |
---|---|---|
原始方案 | 10020 | 320ms |
游標方案 | 20 | 1.2ms |
提升 | 500倍 | 266倍 |
總結:EXPLAIN 優化路線圖
graph TDA[捕獲問題SQL] --> B[運行EXPLAIN]B --> C{關鍵問題點}C -->|type=ALL| D[添加缺失索引]C -->|Using filesort| E[添加排序索引]C -->|DEPENDENT SUBQUERY| F[改寫為JOIN]C -->|全表掃描| G[添加覆蓋索引]C -->|高rows值| H[優化查詢條件]C -->|Using temporary| I[優化GROUP BY]D & E & F & G & H & I --> J[重新EXPLAIN驗證]J --> K{性能達標?}K -->|是| L[完成]K -->|否| B
通過系統分析 EXPLAIN
結果,遵循 “減少掃描行數” 和 “避免額外操作” 兩大原則,可解決大多數 SQL 性能問題。建議將 EXPLAIN
作為 SQL 上線前的標準檢查項。