SQL性能优化实战:5个让查询提速10倍的技巧(2026版)
作为一个写了近10年SQL的数据分析师,船长见过太多"能跑就行"的SQL。在数据量小的时候,3秒和30秒的差别不大。但当你的表从100万行涨到1亿行,那30秒就变成了30分钟——甚至直接超时报错。
今天不聊理论,只聊船长在生产环境里实打实用过的5个优化技巧。每一个都经过验证,有具体案例和性能数据。
技巧1:用EXPLAIN先诊断,别瞎优化
80%的性能问题,用EXPLAIN一看就知道。
核心看3个字段:
① type:从好到差:const > eq_ref > ref > range > index > ALL。如果看到ALL(全表扫描),这就是性能瓶颈。
② rows:MySQL预估要扫描的行数。如果这个数字远大于你的预期结果行数,说明索引没走对。
③ Extra:如果看到"Using filesort"或"Using temporary",说明MySQL在用临时表或文件排序,性能杀手。
案例:一次线上排查,一个用户行为分析查询耗时47秒。EXPLAIN发现type=ALL,rows=8500万。加了一个复合索引后,查询降到0.3秒,提速157倍。
-- 优化前:全表扫描,47秒
SELECT user_id, COUNT(*) as cnt
FROM user_behavior
WHERE action_time >= '2026-04-01'
GROUP BY user_id;
-- 优化后:加复合索引,0.3秒
ALTER TABLE user_behavior
ADD INDEX idx_action_time_user (action_time, user_id);
技巧2:避免SELECT *,只查需要的列
这是一个"所有人都知道但没人执行"的规则。SELECT * 有两个致命问题:
① 网络传输浪费:如果你只需要user_id和name,但表里有20个字段(包括TEXT、BLOB),MySQL会把所有数据从磁盘读到内存,再通过网络传输给你。多余的数据全是浪费。
② 无法走覆盖索引:如果你只查索引列,MySQL可以直接从索引树返回数据,不需要回表。这就是"覆盖索引"的威力。
-- 优化前:回表查询,扫描230万行
SELECT * FROM orders WHERE status = 'paid';
-- 优化后:覆盖索引,不回表
SELECT id, user_id, amount, status
FROM orders
WHERE status = 'paid';
-- 建立覆盖索引
ALTER TABLE orders
ADD INDEX idx_status_cover (status, id, user_id, amount);
实测:一张2300万行的订单表,SELECT * 耗时8.2秒,只查4列 + 覆盖索引后耗时0.15秒,提速55倍。
技巧3:子查询改JOIN,性能提升立竿见影
很多SQL新手喜欢用子查询,写起来简洁,但MySQL对子查询的优化能力有限。特别是WHERE子句中的IN子查询,往往会被执行为"依赖子查询"——对外层每一行都执行一次内层查询。
-- 优化前:子查询,12.5秒
SELECT * FROM users
WHERE id IN (
SELECT user_id FROM orders
WHERE amount > 1000
);
-- 优化后:改JOIN,0.8秒
SELECT DISTINCT u.*
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.amount > 1000;
-- 更好的写法:先GROUP BY再JOIN,减少JOIN的数据量
SELECT u.*
FROM users u
INNER JOIN (
SELECT user_id FROM orders
WHERE amount > 1000
GROUP BY user_id
) o ON u.id = o.user_id;
实测:500万用户 × 3000万订单,子查询12.5秒 → JOIN 0.8秒,提速16倍。
技巧4:用LIMIT控制中间结果集
分页查询是性能黑洞。当你的OFFSET很大时(比如LIMIT 100000, 20),MySQL需要扫描前100020行,然后丢弃前100000行,只返回最后20行。
优化方案:游标分页(延迟关联)
-- 优化前:OFFSET分页,越往后越慢
SELECT * FROM articles
ORDER BY created_at DESC
LIMIT 100000, 20;
-- 优化后:游标分页,用上一页最后一条的ID
SELECT * FROM articles
WHERE id < 上一次最后一条的id
ORDER BY id DESC
LIMIT 20;
-- 延迟关联:先查主键再回表
SELECT a.*
FROM articles a
INNER JOIN (
SELECT id FROM articles
ORDER BY created_at DESC
LIMIT 100000, 20
) tmp ON a.id = tmp.id;
实测:100万行数据,LIMIT 100000时,普通分页3.8秒 → 延迟关联0.04秒,提速95倍。
技巧5:合理使用UNION ALL替代UNION
UNION和UNION ALL的区别:UNION会自动去重(执行DISTINCT操作),UNION ALL不会。
如果你确定两个查询结果没有重复行(或者不需要去重),直接用UNION ALL。UNION的去重操作需要创建临时表、排序、去重,对于大数据集非常消耗性能。
-- 优化前:UNION去重,8.6秒
SELECT user_id, 'mobile' as source FROM mobile_users
UNION
SELECT user_id, 'web' as source FROM web_users;
-- 优化后:UNION ALL不去重,0.7秒
SELECT user_id, 'mobile' as source FROM mobile_users
UNION ALL
SELECT user_id, 'web' as source FROM web_users;
实测:两个500万行的表UNION,8.6秒 → UNION ALL 0.7秒,提速12倍。
性能优化速查表
| 优化技巧 | 提速倍数 | 适用场景 |
|---------|---------|---------|
| EXPLAIN诊断 + 加索引 | 10-160倍 | 全表扫描 |
| 只查需要的列 + 覆盖索引 | 10-55倍 | 大表宽表 |
| 子查询改JOIN | 5-16倍 | 关联查询 |
| 游标分页替代OFFSET | 10-95倍 | 深分页 |
| UNION ALL替代UNION | 5-12倍 | 合并结果集 |
总结
SQL优化不是玄学,是工程。船长总结了一个简单流程:
👉 第一步:EXPLAIN看执行计划,定位瓶颈
👉 第二步:检查是否有全表扫描(type=ALL)
👉 第三步:加合适的索引(单列/复合/覆盖)
👉 第四步:改写SQL(子查询→JOIN、UNION→UNION ALL)
👉 第五步:再次EXPLAIN验证优化效果
记住一句话:数据不说谎,但会误导人。EXPLAIN才是SQL优化的第一工具。
我是船长,一个在数据分析这行干了近10年的实战派。专注分享SQL、Python、数据分析的避坑经验。关注我,少走弯路。
&spm=1001.2101.3001.5002&articleId=160467122&d=1&t=3&u=dd4a1cc4ac784014b2a9386c8cffd905)
685

被折叠的 条评论
为什么被折叠?



