翻车案例:用Explain解读慢查询的死亡密码
🎬 故事开场:当数据库加班到凌晨三点
场景:某电商系统在"双11"当天,查询"用户购买记录"的SQL突然变慢,数据库CPU飙到99%,运维工程师小王被紧急叫醒。经查,罪魁祸首是一条看似无害的SQL:
sql
SELECT * FROM orders
WHERE user_id = 12345
AND order_time BETWEEN '2023-11-11 00:00:00' AND '2023-11-11 23:59:59';
🔍 用Explain破解慢查询之谜
1. Explain语法与输出字段解读
sql
EXPLAIN FORMAT=JSON
SELECT * FROM orders
WHERE user_id = 12345
AND order_time BETWEEN '2023-11-11 00:00:00' AND '2023-11-11 23:59:59';
关键输出解析(以MySQL为例):
json
{
"query_block": {
"select_id": 1,
"table": {
"table_name": "orders",
"access_type": "ALL", -- ❌ 全表扫描!
"possible_keys": null, -- 索引失踪案!
"rows": 10000000 -- 扫描1亿行???
}
}
}
Mermaid图示(查询执行计划):
graph TD
A[全表扫描] -->|扫描1亿行| B[数据库崩溃]
C[没有索引] -->|CPU飙车| B
🚨 典型慢查询翻车现场(附修复代码)
案例1:索引失踪案
问题SQL:
sql
SELECT * FROM orders
WHERE user_id = 12345
AND order_time > '2023-01-01';
Explain诊断:
json
"possible_keys": null
修复方案:
sql
-- 创建联合索引(注意字段顺序!)
ALTER TABLE orders ADD INDEX idx_user_time (user_id, order_time);
案例2:Like的致命诱惑
问题SQL:
sql
SELECT * FROM users
WHERE name LIKE '%张三%';
Explain诊断:
json
"rows": 5000000, "filtered": 100.00
修复方案:
sql
-- 全局搜索考虑全文索引(MySQL 5.7+)
ALTER TABLE users ADD FULLTEXT(name);
SELECT * FROM users
WHERE MATCH(name) AGAINST('+张三*' IN BOOLEAN MODE);
案例3:隐形排序陷阱
问题SQL:
sql
SELECT order_id, user_id FROM orders
ORDER BY create_time
LIMIT 10;
Explain诊断:
json
"filesort": true
修复方案:
sql
-- 在ORDER BY字段上添加索引
ALTER TABLE orders ADD INDEX idx_create_time (create_time);
🧠 常见面试题 & 答案
题1:Explain的access_type
有哪些类型?优先级如何?
答案:
- 优先级从高到低 :
system
(单行) >const
(主键单行) >eq_ref
(唯一索引) >ref
(非唯一索引) >range
(范围查询) >index
(索引扫描) >ALL
(全表扫描)
- 考场杀手锏 :看到
ALL
直接喊"优化!"
题2:为什么WHERE id=1 AND name='张三'
可能不走索引?
答案:
- 索引合并失效 :若没有联合索引
(id,name)
,单独的单字段索引会被随机选择一个,另一个条件退化为回表查询 - 实战演示:
sql
-- 错误写法(单字段索引)
CREATE INDEX idx_id ON users(id);
CREATE INDEX idx_name ON users(name);
-- 正确写法(联合索引)
ALTER TABLE users ADD INDEX idx_id_name (id, name);
题3:Using temporary; Using filesort
如何优化?
答案:
- 优化路径 :
- 确保
ORDER BY
字段有索引 - 确保
GROUP BY
字段有索引 - 避免在
ORDER BY
中使用函数(如ORDER BY LOWER(name)
)
- 确保
- 代码示例:
sql
-- 原始SQL(触发filesort)
SELECT department, COUNT(*)
FROM employees
GROUP BY department
ORDER BY department DESC;
-- 优化后(添加索引)
ALTER TABLE employees ADD INDEX idx_department (department);
题4:覆盖索引是什么?如何验证?
答案:
- 定义:查询字段全在索引中,避免回表查询
- 验证方法:
sql
EXPLAIN SELECT user_id, order_time
FROM orders
WHERE user_id = 12345; -- 若显示"Using index"即为覆盖索引
题5:为什么SELECT *
比SELECT id
慢?
答案:
- 数据量差异 :
SELECT *
读取整行数据,而SELECT id
仅取单字段 - 索引覆盖 :若存在覆盖索引
(id)
,SELECT id
可直接从索引中获取,无需回表
🛠️ 代码实验室:动手优化慢查询
实验环境搭建(MySQL)
sql
-- 创建测试表
CREATE TABLE `orders` (
`id` bigint PRIMARY KEY AUTO_INCREMENT,
`user_id` int NOT NULL,
`order_time` datetime NOT NULL,
`status` varchar(16) NOT NULL,
INDEX idx_user_time (user_id, order_time)
) ENGINE=InnoDB;
-- 插入测试数据
INSERT INTO orders (user_id, order_time, status)
VALUES (12345, '2023-11-11 00:00:00', 'PAID'),
(67890, '2023-11-11 01:00:00', 'CANCELLED');
实验任务:
- 执行慢查询:
sql
EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
- 预期输出:
possible_keys: NULL
(因为status无索引)
- 优化并验证:
sql
ALTER TABLE orders ADD INDEX idx_status (status);
EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
-- 应显示key: idx_status
💡 面试官心理战
当面试官问 :"如果优化后的SQL还是慢,怎么办?"
反杀回答:"我会检查索引碎片化(执行ANALYZE TABLE)、查看慢查询日志、甚至考虑分库分表------毕竟数据库加班到凌晨三点,它也需要个按摩师(读写分离)!"