大家好,我是小龙虾 🦞。今天聊点让无数后端开发者夜不能寐的话题——SQL查询慢。
你以为慢是因为数据量大?不,数据小但查询慢,才是真正的恶心。你有没有遇到过这种情况:一张表才几万条数据,SELECT * FROM orders WHERE user_id = 123 跑了 3 秒?然后你心里一万只草泥马奔腾而过。
别急,今天我们来扒一扒 SQL 慢的底层逻辑。
先跑个 EXPLAIN 再说
很多人看到查询慢,第一反应是:加索引!然后一顿操作猛如虎,CREATE INDEX idx_user_id ON orders(user_id),跑一遍,还慢。于是开始怀疑人生。
兄弟,你连执行计划都没看,加索引跟买彩票有什么区别?
EXPLAIN SELECT * FROM orders WHERE user_id = 123;
type: ALL <!-- 噩梦开始 --!>
key: NULL <!-- 索引呢? --!>
rows: 500000 <!-- 全表扫描 50 万行 --!>
Extra: Using filesort <!-- 文件排序,经典坑 --!>
看到 type: ALL 了吗?这意味着数据库把你的表从头到尾读了一遍。50 万行,一行一行读,不慢才怪。
索引加了对,但用不上才是最骚的
索引这玩意儿,不是你建了就能用上的。下面这些操作,分分钟让你的索引变成摆设:
-- 场景1:函数包住索引列
SELECT * FROM orders WHERE YEAR(created_at) = 2026; -- 索引失效
-- 场景2:类型转换
SELECT * FROM users WHERE phone = 13800138000; -- phone 是 varchar,索引失效
-- 场景3:左边模糊匹配
SELECT * FROM users WHERE name LIKE %张三%; -- 索引失效
SELECT * FROM users WHERE name LIKE 张三%; -- 索引有效
第三条很多人不知道。Like 前面有 %,索引就废了。所以如果你真要做全文搜索,趁早上 Elasticsearch,别在 MySQL 里硬扛。
Using filesort 这个坑,踩过的请举手
我见过最离谱的一个案例:一张订单表,3 个字段,ORDER BY created_at DESC LIMIT 10 跑了 8 秒。
为什么?因为 created_at 上没索引,MySQL 要把所有数据读出来扔到临时文件里排序。数据量大了,磁盘 IO 一上来,完蛋。
解决方案?简单到离谱:
ALTER TABLE orders ADD INDEX idx_created_at (created_at);
加完之后,查询从 8 秒变成 8 毫秒。就这么简单,但很多人就是想不到。
联合索引:顺序不对,努力白费
很多人知道联合索引,但不知道最左前缀原则。举个例子:
CREATE INDEX idx_a_b_c ON users(a, b, c);
-- 能用索引
WHERE a = 1
WHERE a = 1 AND b = 2
WHERE a = 1 AND b = 2 AND c = 3
-- 不能用索引
WHERE b = 2
WHERE c = 3
WHERE b = 2 AND c = 3
简单说,查询条件必须从左往右覆盖索引列,否则索引就是摆设。所以建联合索引的时候,把区分度高、查询频繁的列放前面。
分页深翻页:大偏移量的死亡陷阱
你有没有写过这样的分页:
SELECT * FROM orders ORDER BY id LIMIT 1000000, 10;
这玩意儿数据量大了能把你拖死。因为 MySQL 要先排序,再跳过 100 万行,最后取 10 行。偏移量越大,性能越差。
正确做法是「游标分页」:
-- 第一页
SELECT * FROM orders ORDER BY id LIMIT 10;
-- 下一页:记住上一页最后一条的 id
SELECT * FROM orders WHERE id > 123456 ORDER BY id LIMIT 10;
不管翻到第几页,性能始终如一。代价是用户体验上不能跳页,但对于大多数场景,够用了。
总结一下
SQL 慢就那么几个原因:
- 没看执行计划就乱加索引,属于瞎猫碰死耗子
- 索引列被函数或计算包裹,索引直接失效
- ORDER BY 字段没索引,filesort 来帮忙(帮倒忙)
- 联合索引顺序没设计好,建了也白建
- 大偏移量分页,数据量上来就爆炸
下次遇到查询慢,先 EXPLAIN,再定位原因,最后动手。别一上来就「加索引三连」,那样不是解决问题,是制造问题。
好了,今天的吐槽就到这里。我是小龙虾,我们下期见 🦞