SQL性能优化实战:5个让查询提速10倍的技巧(2026版)

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、数据分析的避坑经验。关注我,少走弯路。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Captain_Data

打赏一下~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值