LOGO 首页 OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 技术文档 其他文档  
 
网站管理员

SQL 写完后必须自查的 10 条军规:避开 90% 的慢查询

admin
2026年8月6日 18:12 本文热度 133

上次我们聊了“SQL 为什么会越查越慢”,讲了 5 个最容易踩的坑。

评论区最多的一条反馈是:“道理我都懂,但写的时候还是忘。”

所以今天这一篇,我把它做成一份可以照着用的 Check List——10 条军规,每条都给出:

  • • 一句话判断标准(Yes/No)
  • • 错误写法示例
  • • 正确写法示例
  • • 对应的 EXPLAIN 关键字段

你可以收藏到代码片段里,写完 SQL 直接对一遍;也能贴在 Code Review 模板前,每次合 PR 前扫一遍。

测试环境:MySQL 8.0.32,EXPLAIN FORMAT=TRADITIONAL


军规 0:先看 EXPLAIN,再决定要不要优化

不是所有 SQL 都需要优化。但所有上线的 SQL 都应该被 EXPLAIN 过一遍

EXPLAIN SELECT ... ;

重点关注 4 个字段:

字段
警惕值
typeALL
(全表扫描)、index(全索引扫描)
keyNULL
,说明没走索引
rows
远大于实际返回行数(比如查 10 行扫 100 万行)
Extra
出现 Using filesortUsing temporary

如果 4 个字段都健康,这条 SQL 可以直接发布,不用再纠结。


军规 1:能用索引,就别全表扫

判断标准type 不是 ALL / index,且 key 不为 NULL

错误写法

SELECT id, name FROM users WHERE status = 1;

如果 status 只有 0/1 两个值,索引区分度太低,MySQL 优化器会放弃索引。

正确写法

-- 方案 A:组合索引,把区分度低的字段放后面
ALTER TABLE
 users ADD INDEX idx_created_status (created_at, status);

SELECT
 id, name
FROM
 users
WHERE
 created_at >= '2026-07-01'
  AND
 status = 1;

-- 方案 B:枚举型筛选交给 ES / 归档表

一句话:索引不是越多越好,而是要让 MySQL 觉得“值得用”。


军规 2:索引列别参与计算、函数、类型转换

判断标准:WHERE 条件里的字段是“裸字段”,没有被函数包过。

错误写法

-- 索引列上用了函数
SELECT
 user_id FROM login_log
WHERE
 DATE(login_time) = CURDATE();

-- 字符串字段传了数字(隐式类型转换)

SELECT
 * FROM orders WHERE mobile = 13800000000;

正确写法

-- 把函数挪到常量侧
SELECT
 user_id FROM login_log
WHERE
 login_time >= CURDATE()
  AND
 login_time <  CURDATE() + INTERVAL 1 DAY;

-- 类型保持一致

SELECT
 * FROM orders WHERE mobile = '13800000000';

执行计划对比:type: ALL → type: range

一句话:任何让索引列“变形”的写法,索引都会失效。


军规 3:LIKE 别让通配符打头

判断标准LIKE '前缀%',永远不要 LIKE '%关键词%'

错误写法

SELECT id, name FROM products WHERE name LIKE '%牛奶%';

执行计划:

type: ALL
key: NULL

正确写法

-- 简单搜索:让前缀是确定的
SELECT
 id, name FROM products WHERE name LIKE '牛奶%';

-- 复杂搜索:用全文索引(中文需配合 ngram)

ALTER TABLE
 products
  ADD
 FULLTEXT INDEX ft_name (name) WITH PARSER ngram;

SELECT
 id, name
FROM
 products
WHERE
 MATCH(name) AGAINST('+牛奶' IN BOOLEAN MODE);

-- 终极方案:丢给 Elasticsearch / Meilisearch

军规 4:OR 改 IN,跨列就别用 OR

判断标准:能用 IN 就别用 OR;跨列 OR 直接重写为 UNION ALL

错误写法

SELECT id FROM orders
WHERE
 status = 'PENDING' OR status = 'CANCELLED';

某些版本能走 index_merge,但跨列 OR 几乎一定走全表:

-- 跨列 OR:典型反模式
SELECT
 id FROM orders
WHERE
 status = 'PENDING' OR user_id = 1001;

正确写法

-- 同列 OR 改 IN
SELECT
 id FROM orders
WHERE
 status IN ('PENDING', 'CANCELLED');

-- 跨列 OR 拆成 UNION ALL

SELECT
 id FROM orders WHERE status = 'PENDING'
UNION
 ALL
SELECT
 id FROM orders WHERE user_id = 1001;

军规 5:SELECT * 是慢查询的温床

判断标准:SELECT 后面只列出真正用到的列

反模式

SELECT * FROM orders WHERE id = 1001;

如果表里有 TEXT / JSON / BLOB 字段,会把这些大字段一起读出来,走网络、走 ORM 映射、走 JSON 序列化,都会变慢。

正确写法

SELECT id, user_id, amount, status, created_at
FROM
 orders WHERE id = 1001;

执行计划对比:返回同样行数,Extra 会少 Using where 后面的隐式开销。

一句话:写多少字段,就取多少字段。ORM 里的 SELECT * 是隐藏的慢查询。


军规 6:分页别用大 offset

判断标准LIMIT N, M 中 N < 1000;否则用游标分页

反模式

SELECT *
FROM
 orders
WHERE
 status = 'PAID'
ORDER
 BY created_at DESC
LIMIT 1000000, 20;

执行计划:

type: ALL
rows: 5000000
Extra: Using where; Using filesort

正确写法

-- 游标分页:上一次的最大 created_at 作为起点
SELECT
 *
FROM
 orders
WHERE
 status = 'PAID'
  AND
 created_at < '2026-08-01 10:00:00'
ORDER
 BY created_at DESC
LIMIT 20;

-- 数据量再大一点:先取 id,再回表

SELECT
 o.*
FROM
 orders o
INNER
 JOIN (
  SELECT
 id FROM orders
  WHERE
 status = 'PAID'
  ORDER
 BY created_at DESC
  LIMIT 1000000, 20
) t USING (id);

军规 7:COUNT(*) 不一定慢,但要选对写法

判断标准:MySQL 8.0 直接用 COUNT(*);不要在 COUNT 里塞表达式。

反模式

-- 给每行做一次计算
SELECT
 COUNT(DISTINCT user_id) FROM orders;

-- 用子查询套一层

SELECT
 COUNT(*) FROM (SELECT user_id FROM orders) t;

正确写法

-- 8.0 下 COUNT(*) 已经被优化为最优路径
SELECT
 COUNT(*) FROM orders WHERE status = 'PAID';

-- 复杂去重才用 DISTINCT,但要先确认业务真需要

SELECT
 COUNT(DISTINCT user_id) FROM orders;

军规 8:JOIN 别超过 3 张表,带上连接条件

判断标准

  • • JOIN 表数量 ≤ 3
  • • 每个 JOIN 都有 ON
  • • 被驱动表的连接字段必须有索引

反模式

SELECT *
FROM
 a
JOIN
 b ON a.b_id = b.id
JOIN
 c ON b.c_id = c.id
JOIN
 d ON c.d_id = d.id
JOIN
 e ON d.e_id = e.id
WHERE
 a.status = 1;

正确写法

  • • 把能拆开的统计 / 汇总提前在应用层做
  • • 业务确实需要多表关联,确认每个 JOIN 字段都有索引
  • • 用 STRAIGHT_JOIN 强制驱动顺序时,先 EXPLAIN 验证
SELECT a.id, b.name, c.value
FROM
 a
JOIN
 b ON a.b_id = b.id            -- b.id 是主键
JOIN
 c ON b.c_id = c.id            -- c.id 是主键
WHERE
 a.status = 1;

军规 9:ORDER BY 要走索引,否则必慢

判断标准Extra 不出现 Using filesort

反模式

SELECT id FROM orders WHERE status = 1 ORDER BY created_at;

如果 status 和 created_at 没有联合索引,MySQL 会先过滤,再排序。

正确写法

-- 联合索引,排序列放最后(最左前缀)
ALTER TABLE
 orders ADD INDEX idx_status_created (status, created_at);

SELECT
 id FROM orders WHERE status = 1 ORDER BY created_at;

执行计划对比:

# 错误
type: ref
Extra: Using where; Using filesort

# 正确
type: ref
Extra: Using where

一句话:索引的顺序就是排序的顺序。


军规 10:批量写别循环单条

判断标准:批量操作 ≥ 100 条时,必须合并

反模式(应用层 for 循环):

for order in orders:
    db.execute("INSERT INTO orders (...) VALUES (...)", order)

1000 条 = 1000 次网络往返 + 1000 次事务提交。

正确写法

# 单条 INSERT 多 VALUES
db.execute("INSERT INTO orders (col1, col2) VALUES (?,?), (?,?), (?,?)", rows)

# 或事务包起来

with
 db.transaction():
    for
 order in orders:
        db.execute("INSERT INTO orders (...) VALUES (...)", order)

一图速查:10 条军规对照表

#
一句话
EXPLAIN 看什么
0
先 EXPLAIN 再优化
4 个关键字段
1
能走索引就别全表扫
type
 不是 ALL
2
索引列别变形
key
 不为 NULL
3
LIKE
 别打头
key
 不为 NULL
4
OR
 改 IN 或 UNION ALL
type: range
5
别 SELECT *
Extra
 没多余开销
6
分页用游标
rows
 不爆炸
7
COUNT(*)
 直接用
走索引就行
8
JOIN ≤ 3
,每张表都有索引
key
 不为 NULL
9
ORDER BY
 走索引
没 Using filesort
10
批量写合并
不走 SQL,事务+批量


把它贴进你的工作流

  • • Code Review 模板:把 10 条做成 PR checklist,不勾完不让合
  • • 上线前 5 分钟:写完 SQL → EXPLAIN 一遍 → 对照军规
  • • 报警来时:先看 type + rows,再回到对应军规改写法
  • • 新人入职:把这篇甩过去,比 100 页的 MySQL 文档有用

写在最后

SQL 优化不是“艺术”,是工程

军规的本质只有一句话:让 MySQL 尽可能少地扫描、尽可能多地走索引、尽可能少地做排序和回表。

记住上面这 10 条,能挡掉 90% 的慢查询;剩下 10% 是真功夫,那就要靠 EXPLAIN 和 SHOW PROFILE 去一点点抠。


阅读原文:点击这里


该文章在 2026/8/6 18:12:21 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2026 ClickSun All Rights Reserved  粤ICP备13012886号-9  粤公网安备44030602007207号