【MySQL】SQL优化实战:从慢查询定位到查询改写

目录

一、优化第零步:先定位,别靠猜

开慢查询日志

分析慢日志

[二、EXPLAIN 深度解读](#二、EXPLAIN 深度解读)

[三、单条 SQL 改写技巧](#三、单条 SQL 改写技巧)

[1. 拒绝 SELECT *](#1. 拒绝 SELECT *)

[2. 深分页](#2. 深分页)

[3. COUNT 优化](#3. COUNT 优化)

[4. ORDER BY 优化(规避 filesort)](#4. ORDER BY 优化(规避 filesort))

[5. GROUP BY 优化(规避 temporary)](#5. GROUP BY 优化(规避 temporary))

[6. JOIN 优化:小表驱动大表](#6. JOIN 优化:小表驱动大表)

[7. 子查询转 JOIN](#7. 子查询转 JOIN)

[8. OR 改 UNION](#8. OR 改 UNION)

[9. 别让索引失效](#9. 别让索引失效)

四、表设计与数据类型优化

[五、完整案例:一条慢 SQL 的救活过程](#五、完整案例:一条慢 SQL 的救活过程)

[六、面试:被问"这条 SQL 怎么优化"的回答套路](#六、面试:被问"这条 SQL 怎么优化"的回答套路)

七、小结


一句话:索引优化是"地基",SQL 优化是"施工"------同样的索引,写法不同性能差百倍。这篇讲的是"拿到一条慢 SQL,怎么一步步把它救活"。


一、优化第零步:先定位,别靠猜

最大的误区是"线上慢了就加索引"。正确姿势是先确认慢在哪、再动手

开慢查询日志

|-----------------------|-------------------------------|
| 参数 | 含义 |
| slow_query_log | 是否开启慢日志 |
| long_query_time | 超过多少秒算"慢"(默认 10s,建议调到 1s 抓隐患) |
| slow_query_log_file | 日志文件路径 |

分析慢日志

⚠️ 没有慢日志就优化,等于蒙眼修车。先 EXPLAIN 那条被抓出来的 SQL,再决定改法。


二、EXPLAIN 深度解读

EXPLAIN SELECT ... 输出的列,面试和实战都看这几项:

|-----------------|--------------------------------------------------------------------------|
| 列 | 看什么 |
| id | 执行顺序,id 相同从上往下,id 越大越先执行(子查询/派生表) |
| select_type | 查询类型:SIMPLE 简单查询、PRIMARY 主查询、SUBQUERY 子查询、DERIVED 派生表 |
| type | 访问类型,核心指标 ,好→坏:system > const > eq_ref > ref > range > index > ALL |
| key | 实际用到的索引,NULL = 没走索引 |
| key_len | 索引使用的字节数,判断联合索引用到了几列 |
| ref | 索引的匹配条件(常量或某列) |
| rows | 预估扫描行数,越小越好 |
| filtered | 存储引擎返回后,Server 层再过滤的比例 |
| Extra | 关键补充信息(见下) |

type 看到 ALL = 全表扫描,红色警报 ;业务查询至少要到 ref / range

Extra 重点

  • Using index ✅ 覆盖索引,最优
  • Using where 正常,引擎返回后再过滤
  • Using index condition ✅ 索引条件下推(ICP),在引擎层就用索引过滤,减少回表
  • Using filesort ⚠️ 额外排序,排序没走索引
  • Using temporary ⚠️ 用了临时表(GROUP BY / DISTINCT 高频中招)

key_len 速算 (utf8mb4 下每字符 4 字节):varchar(20) 可空 → 20*4 + 2(变长) + 1(可空) = 83int4bigint8。用它判断"联合索引到底用到了第几列"。


三、单条 SQL 改写技巧

这部分是"索引建好了但仍慢"的主战场。

1. 拒绝 SELECT *

只查需要的列,才有机会走 覆盖索引,省掉回表 IO。

2. 深分页

LIMIT 100000, 20 要先丢 10 万行。用主键游标延迟关联改:

3. COUNT 优化

COUNT(*)COUNT(1) 等价且最快(MySQL 优化过);COUNT(列) 只数非 NULL。要"近似值"可走 EXPLAINrows 或用 information_schema,别对大表 COUNT(*) 还加各种条件硬扛。

4. ORDER BY 优化(规避 filesort)

排序字段要和索引顺序一致才能走索引排序:

⚠️ ORDER BY 多个字段的方向要一致(ASC/DESC 混用会导致 filesort)。MySQL 8.0 支持降序索引,可以建 (a, b DESC)

5. GROUP BY 优化(规避 temporary)

分组字段走索引,且 WHERE 先过滤再分组。写法上 GROUP BY 的列尽量与 ORDER BY 一致,避免"先分组再排序"的双重代价。

6. JOIN 优化:小表驱动大表

Nested Loop Join 中,外层(驱动表)越小,内层被扫描次数越少 。连接字段必须建索引,否则退化为笛卡尔积。

7. 子查询转 JOIN

相关子查询是 O(n×m)(外层每行跑一次内层)。能用 JOIN / 派生表先聚合再回连的,尽量改:

8. ORUNION

WHERE a=1 OR d=2(d 无索引)常导致整条 SQL 放弃索引走全表。拆成 UNION 各自走索引:

9. 别让索引失效

对列用函数、LIKE '%x'、隐式类型转换、违反最左前缀------这些都会让前面建的索引白建。详见《MySQL索引优化》篇。


四、表设计与数据类型优化

很多慢 SQL 根子在"表设计",靠改写 SQL 救不回来:

  • 选最小够用的类型tinyint 够别用 intint 够别用 bigintvarchar 长度按需给,别一律 varchar(255)
  • 避免 NULL :NULL 让索引、统计、比较都更复杂;用默认值代替(如 0'')。
  • 字符集统一 utf8mb4:避免 JOIN 时因字符集/排序规则不一致触发隐式转换、索引失效。
  • 长字符串用前缀索引INDEX(col(20)),取前 20 字符建索引,省空间;注意前缀选择性要够高。
  • 适度反三范式:高频跨表统计可冗余字段,用空间换 JOIN 开销。

五、完整案例:一条慢 SQL 的救活过程

原始 SQL(0.8s,订单表 500 万行):

第 1 步 · EXPLAINtype=ALL(orders 全表扫描)+ Using filesort + rows≈5000000

第 2 步 · 加联合索引ALTER TABLE orders ADD INDEX idx_status_ct (status, create_time);typerefExtra 出现 Using index condition,排序也走索引,filesort 消失。

第 3 步 · 改深分页:用延迟关联,先查 id 再回表。

第 4 步 · 缩小返回列 :业务其实只要 order_no, amount, name,把 SELECT * 换成具体列,命中覆盖索引、减少回表。

结果:0.8s → 30ms,扫描行数从 500 万降到 20。

这条优化串起了三件事:加对索引(03 篇)→ 改写分页(03 篇)→ 缩小列 + JOIN 优化(本篇)。这就是"定位 → 分析 → 改写 → 验证"的完整闭环。


六、面试:被问"这条 SQL 怎么优化"的回答套路

固定顺序,别一上来就加索引:

  1. 先定位 :慢日志抓出来?EXPLAIN 看一下。
  1. 看执行计划type 是不是 ALLkey 用没用上?rows 多大?Extra 有没有 filesort/temporary?
  1. 看索引是否失效 :函数、隐式转换、LIKE '%x'、最左前缀。
  1. 减数据量:先过滤再 JOIN?覆盖索引避免回表?
  1. 再谈改写:子查询转 JOIN、深分页改游标、OR 改 UNION。
  1. 看表设计:类型/字符集/NULL/反三范式。
  1. 最后才是架构层:缓存、冗余、分库分表。

七、小结

SQL 优化的知识地图:定位(慢日志)→ 解读(EXPLAIN 各列)→ 改写九式(SELECT * / 深分页 / COUNT / ORDER BY / GROUP BY / JOIN / 子查询转 JOIN / OR 改 UNION / 防失效)→ 表设计(类型/NULL/字符集/前缀索引/反三范式)→ 案例闭环 → 面试套路

相关推荐
蓝鸟19741 小时前
Oracle HIS业务视图优化实战|VI_BQ_YWTX11从高耗费到秒级响应
数据库·oracle·his数据库调优·复合索引踩坑·is null索引问题·ql视图优化
数智启示录1 小时前
Apache Doris 4.0.8 混合检索实战(第 5 篇):三套系统只返回两条结果,一条 SQL 如何避开交集丢失
大数据·数据库·经验分享·sql·面试
今天AI了吗1 小时前
大模型技术全景(一):AI、机器学习、深度学习,三者的关系到底是什么?一文搞懂
数据库·人工智能·python·sql·深度学习·机器学习·rust
这个DBA有点耶1 小时前
RANGE/LIST/HASH/二级分区怎么选?分区表适用边界与隐藏代价
数据库·mysql·dba
离陌在学C#2 小时前
C# 异步编程:从 async/await 到 Task 实战指南
开发语言·数据库·c#
还是大剑师兰特2 小时前
MySQL8.0 Windows完整安装教程
windows·mysql·大剑师
2601_962074682 小时前
Spring Boot的无缝衔接:深入解析与实践
数据库·spring boot·后端
武帝为此2 小时前
【InnoDB存储引擎介绍】
数据库·算法
csdn_aspnet3 小时前
mysql 触发器写法及示例
数据库·mysql·触发器·trigger