还找借口?!有90%的慢SQL,都不是因为数据量大
很多人一看到 SQL 跑得慢,第一反应就是:数据量太大了。
这话有时候没错,但更多时候,它只是个看起来很合理的借口。
真实情况往往更扎心: 表不算大,机器也还行,索引甚至都建了,SQL 还是慢。
那问题多半不在“数据量”,而在 SQL 写法把数据库优化器整不会了。
说得更直接一点: 慢 SQL,大多数不是“扛不住”,而是“写得烂”。
今天这篇,就聊几个最常见、最有代表性的坑。你会发现,很多性能问题,根本不是升级机器能解决的。
一、不是数据量大,而是你先让索引失效了
数据库性能优化里,索引像高速公路。
你明明修了路,结果 SQL 一写,自己先把路封了,然后还怪车多。
最典型的几种写法:
比如:
SELECT *FROM ordersWHERE DATE(create_time) = '2026-03-01';
如果 create_time 上有索引,很多人以为这条语句会走索引。
但现实通常是:不会。
因为你对列做了 DATE() 运算,数据库没法直接利用原始索引值,只能老老实实扫大量数据再计算。
更好的写法:
SELECT *FROM ordersWHERE create_time >= '2026-03-01 00:00:00';AND create_time < '2026-03-02 00:00:00';
这类范围查询,才更容易真正利用索引。
比如手机号是字符串类型,你却拿数字去查:
SELECT *FROM user_profileWHERE phone = '13800138000';
看起来只是少打了两个引号,实际上数据库可能要做类型转换。
一旦转换方向不友好,索引利用率就会直接掉下去。
别小看这种低级错误。
线上很多“偶发性慢 SQL”,本质就是这种代码细节导致的。
SELECT *FROM productWHERE product_name LIKE '%咖啡';
LIKE '%xxx' 这种前面带 % 的写法,B+ 树索引基本帮不上忙。
因为它无法从有序前缀开始定位,只能靠扫描。
如果你的业务里这种查询非常常见,那就别硬拿普通索引扛:
要么改业务检索方式
要么上倒排索引 / 全文检索
要么单独设计搜索服务
别拿数据库当搜索引擎,还怪它不够快。
二、不是数据多,而是你查了太多没用的数据
很多 SQL 慢,问题甚至不在条件,而在你拿太多了。
SELECT *FROM ordersWHERE user_id = 1001;
这句写起来最省事,后果通常最不省事。
为什么?
读取了根本用不到的列
增加了磁盘 IO
增加了网络传输
可能让覆盖索引失效
如果你真正只需要:
SELECT order_id, amount, statusFROM ordersWHERE user_id = 1001;
那就老老实实只查这三列。
很多场景下,字段一收窄,性能就肉眼可见地好起来。
有些接口明明前端只展示 20 条,你后端 SQL 却先捞 5 万条回来,再在代码里慢慢处理。
这不是数据库慢,这是你对数据库不太客气。
该加 LIMIT 的地方就加,
该做分页的地方就分页,
该做游标翻页的地方别硬上深分页。
三、不是数据量问题,而是 JOIN / 子查询写得太任性
SQL 慢,不少时候慢在“关系太复杂”,不是“数据太大”。
如果多表关联时,过滤条件没有尽早生效,数据库可能会先生成很大的中间结果集,再一步步筛。
这就像你先把全公司的人都拉进会议室,再问一句“做后端的留下”。
不慢才怪。
优化思路通常包括:
先过滤再关联
给关联列加合适索引
避免无谓的宽表 JOIN
看执行计划,确认驱动表是否合理
比如相关子查询:
SELECT u.id,(SELECT COUNT(*) FROM orders o WHERE o.user_id = u.id) AS order_cntFROM users u;
如果外层 users 记录很多,内层子查询可能被反复执行。
在一些场景下,改成聚合后再 JOIN,性能会稳定很多:
SELECT u.id, t.order_cntFROM users uLEFT JOIN (SELECT user_id, COUNT(*) AS order_cntFROM ordersGROUP BY user_id) t ON u.id = t.user_id;
当然,具体还要看数据库版本、优化器能力和实际执行计划。
但有一点不会变: 别只看 SQL 能不能跑,要看它到底怎么跑。
四、不是表太大,而是排序、分组没借上索引的力
SELECT id, user_id, create_timeFROM ordersWHERE status = 'paid';ORDER BY create_time DESC;
如果 status、create_time 上没有形成合适的联合索引,数据库就可能先筛选,再做额外排序。
数据一多,排序成本非常高,甚至会产生临时文件。
很多人看到慢,就说“因为订单表太大”。
其实更准确的说法是: 你想排序,但没给数据库一条能顺着走的路。
分组统计本身就有成本。
如果还能顺着索引分组,成本会低很多;
如果不能,就容易出现临时表、额外排序、CPU 飙高。
所以别只盯着 where 条件建索引,
ORDER BY、GROUP BY 的字段顺序,也常常决定了这条 SQL 的生死。
五、不是数据量大,而是分页方式错了
很多后台系统一上来就是这种分页:
SELECT *FROM ordersORDER BY idLIMIT 100000, 20;
这类深分页,页数越往后越慢。
原因不是数据库突然变笨了,而是它需要先跳过前面大量数据,再拿后面的 20 条。
这就像让人从一本 10 万页的书里,先翻过前 99980 页,再给你看最后 20 页。
更稳的方式通常是基于游标或主键翻页:
SELECT *FROM ordersWHERE id > 100000ORDER BY idLIMIT 20;
如果业务允许,这种方式通常比 LIMIT offset, size 可靠得多。
六、不是数据库不行,而是你根本没看执行计划
很多性能问题,讨论半天都停留在猜测层面:
是不是数据太大?
是不是数据库版本不行?
是不是机器配置不够?
是不是该分库分表了?
结果一看 EXPLAIN,发现压根没走索引。
这就像病人咳嗽两声,你直接提议换器官。
先看这几个关键信号:
type:是不是全表扫描
key:实际用了哪个索引
rows:预估扫描了多少行
Extra:有没有 Using filesort、Using temporary
很多所谓的大问题,执行计划一拉出来,十分钟就能定位。
别上来就谈架构升级。
先看看是不是一条 SQL 写得不太像样。
七、什么时候“数据量大”才真的是核心问题?
说了这么多,不是要否认数据量的影响。
数据量当然会带来性能压力,尤其在这些场景里:
热数据非常集中
单表达到千万、上亿级别
统计分析型查询很多
索引体积巨大,缓存命中下降
写入频繁,索引维护成本高
但重点是: 很多团队还没到那个量级,就已经先把 SQL 写出了亿级数据的灾难感。
也就是说,本来是“自行车通勤”,
非要把自己骑出“横穿无人区”的痛苦体验。
八、排查慢 SQL,建议按这 5 步走
如果你想系统一点排查,建议按这个顺序:
不要猜,先确认有没有走索引、扫描了多少行、有没有临时表和额外排序。
重点查:
函数操作
隐式转换
前导模糊匹配
不合理的联合索引顺序
能不能去掉 SELECT *?
能不能缩小结果集?
能不能提前过滤?
很多 SQL 慢,不在 where,而在 join / order by / group by。
如果 SQL 写法已经比较健康,索引也合理,执行计划也没明显问题,这时候再考虑分库分表、冷热分离、缓存、搜索引擎替代等方案。
顺序别反了。
一上来就上大手术,通常只是把一个小毛病包装成大项目。
结语
慢 SQL 这件事,最误导人的一句话就是: “因为数据量大。”
它听起来专业,甩锅也方便,甚至还能顺便为后续买机器、上中间件、做复杂架构铺垫。
但很多时候,真相没那么宏大。
真相往往是:
索引失效了
查多了
排序方式不对
分页写法不对
执行计划压根没看
所以,下次再遇到慢 SQL,先别急着怪数据量。
先问一句更实际的话: 这条 SQL,到底是不是“像个懂事的 SQL”?
这个问题,通常比“表有多大”更接近真相。