翻车案例:用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)、查看慢查询日志、甚至考虑分库分表------毕竟数据库加班到凌晨三点,它也需要个按摩师(读写分离)!"


相关推荐
乄夜4 小时前
嵌入式面试高频(5)!!!C++语言(嵌入式八股文,嵌入式面经)
c语言·c++·单片机·嵌入式硬件·物联网·面试·职场和发展
拉不动的猪6 小时前
安卓和ios小程序开发中的兼容性问题举例
前端·javascript·面试
wandongle8 小时前
HTML面试整理
前端·面试·html
liang_jy9 小时前
观察者模式
设计模式·面试
JiangJiang10 小时前
🔥 面试官:Webpack 为什么能热更新?你真讲得清吗?
前端·面试·webpack
蒟蒻小袁10 小时前
力扣面试150题--被围绕的区域
leetcode·面试·深度优先
掘金安东尼10 小时前
字节-Trae、阿里-通义灵码、腾讯-CodeBuddy,为什么都在“卷”AI编码?
面试·llm·github
spionbo12 小时前
Vue 表情包输入组件实现代码及完整开发流程解析
前端·javascript·面试
天涯学馆12 小时前
前后端分离的 API 设计:技术深度剖析
前端·javascript·面试
异常君13 小时前
Spring 中的 FactoryBean 与 BeanFactory:核心概念深度解析
java·spring·面试