翻车案例:用Explain解读慢查询的死亡密码


翻车案例:用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如何优化?

答案

  • 优化路径
    1. 确保ORDER BY字段有索引
    2. 确保GROUP BY字段有索引
    3. 避免在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');

实验任务:

  1. 执行慢查询
sql 复制代码
EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
  • 预期输出:possible_keys: NULL(因为status无索引)
  1. 优化并验证
sql 复制代码
ALTER TABLE orders ADD INDEX idx_status (status);
EXPLAIN SELECT * FROM orders WHERE status = 'PAID';
-- 应显示key: idx_status

💡 面试官心理战

当面试官问 :"如果优化后的SQL还是慢,怎么办?"
反杀回答:"我会检查索引碎片化(执行ANALYZE TABLE)、查看慢查询日志、甚至考虑分库分表------毕竟数据库加班到凌晨三点,它也需要个按摩师(读写分离)!"


相关推荐
Lee川11 小时前
优雅进化的JavaScript:从ES6+新特性看现代前端开发范式
javascript·面试
Lee川15 小时前
从异步迷雾到优雅流程:JavaScript异步编程与内存管理的现代化之旅
javascript·面试
晴殇i17 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
绝无仅有17 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有17 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
AAA梅狸猫18 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫18 小时前
Handler基本概念
面试
Wect19 小时前
浏览器缓存机制
前端·面试·浏览器
掘金安东尼20 小时前
Fun with TypeScript Generics:玩转 TS 泛型
前端·javascript·面试
掘金安东尼20 小时前
Next.js 企业级落地
前端·javascript·面试