为什么你的SQL慢得像蜗牛?——数据库索引的8个反直觉陷阱

2026-09-29 11 0

上周五晚上11点,我被一条SQL叫醒了。

不是开玩笑,是真的被叫醒——监控报警,数据库CPU飙升99%,核心接口超时。我爬起来一看,一条看起来人畜无害的查询:

SELECT * FROM orders WHERE DATE(created_at) = '2026-09-15' AND status = 1 LIMIT 100;

等等,这查询有问题吧?等等,先不急,让我慢慢道来。

陷阱一:DATE()函数——索引杀手本杀

这条查询的WHERE条件里用了DATE(created_at),对日期列套了个函数。在MySQL里,这相当于对索引列说:"你先把这个字段的所有值都算一遍DATE(),然后再比较"。

换句话说——你的索引白建了。

MySQL手册里有一句话很多人没注意:"如果对列使用了函数,索引将不会被使用。"但现实是,更多人根本不知道自己在用函数。

反例(索引失效):

SELECT * FROM orders WHERE DATE(created_at) = '2026-09-15';
SELECT * FROM users WHERE YEAR(birthday) = 1995;
SELECT * FROM logs WHERE MONTH(created_at) = 6;

正例(索引生效):

SELECT * FROM orders WHERE created_at >= '2026-09-15 00:00:00' AND created_at < '2026-09-16 00:00:00';

Range查询,永远的神。

陷阱二:以为建了索引就万事大吉

很多人以为索引是"建了就能用"的。我见过最离谱的一个案例:一个表有17个索引,开发者以为反正我建了索引了,查询肯定快。

结果呢?写入性能差到爆炸,每次INSERT要更新17个B+树,数据库CPU 100%稳稳的。

索引不是免费的午餐。每一张索引都是一颗B+树,每次数据变更都要维护这颗树。索引越多,写入越慢,存储越大。

所以——

  • 别建"以防万一"的索引
  • 定期清理无用索引(可以用pt-duplicate-key-checker)
  • 一个表超过5个索引就要警惕了

陷阱三:联合索引的顺序——你以为的只是你以为

很多人知道"联合索引最左前缀原则",但真正理解这个原则的没几个。

假设有一个联合索引 (status, created_at),那么:

-- 能用索引
SELECT * FROM orders WHERE status = 1;
SELECT * FROM orders WHERE status = 1 AND created_at > '2026-01-01';

-- 索引失效!没有最左列
SELECT * FROM orders WHERE created_at > '2026-01-01';

但问题是,很多人以为WHERE status = 1 AND created_at > ... 能用到整个索引,实际上MySQL只会用到status这一列,后面的created_at是用范围查询断掉的,后面的列就拜拜了。

所以联合索引的列顺序,要把区分度高的列放前面,而不是按照"业务逻辑"随意排。

陷阱四:LIKE的前缀匹配——搜索引擎不是万能的

LIKE %keyword 这种写法,索引是肯定用不了的。但有趣的是,很多人知道这个却不知道另一个坑:

如果你的字段是VARCHAR编码,而你的查询条件是数字类型,MySQL会隐式类型转换,把字符串转成数字——然后索引又没了。

-- phone是VARCHAR类型
SELECT * FROM users WHERE phone = 13800138000;  -- 隐式转换,索引失效!
SELECT * FROM users WHERE phone = '13800138000';  -- 正确

这种bug在线上跑三个月都不会被发现,直到某天某条数据刚好查不到,客诉就来了。

陷阱五:SELECT * —— 懒程序员的标配

SELECT * 一次查询返回所有列,这对索引有什么影响?

如果你的查询能用覆盖索引(索引包含了你要的所有列),MySQL只需要扫描索引树,不需要回表,性能提升巨大。但SELECT * 意味着你肯定要回表,索引的优势直接打七折。

所以:只查你需要的列,这不是装逼,这是性能优化。

陷阱六:以为数据量小就不需要索引

"才几万条数据,查询快得很,不用索引。"这是我听过最傻的话之一。

数据量小的时候,全表扫描可能确实够快。但你的业务不会永远只有几万条数据。更重要的是——开发阶段的习惯会带到生产环境,等你反应过来,数据库里已经堆了千万条数据,查询从0.01秒变成30秒。

索引要从一开始就设计好,别等出问题再打补丁。

陷阱七:OR查询——你以为能用索引,其实不能

看这个查询:

SELECT * FROM orders WHERE user_id = 100 OR status = 1;

如果user_id有索引,status也有索引,这条查询能同时用两个索引吗?答案是:MySQL 5.6之前不能,5.6之后是INDEX MERGE,但效果往往不如你想的好。

很多时候MySQL会选择只用一个索引,或者干脆全表扫描。更好的做法是拆成两条查询用UNION:

SELECT * FROM orders WHERE user_id = 100
UNION ALL
SELECT * FROM orders WHERE status = 1 AND user_id != 100;

陷阱八:以为EXPLAIN是万能的

EXPLAIN是排查SQL性能问题的神器,但很多人对它的解读是错的。

看一个EXPLAIN结果:

type: ALL
key: NULL
rows: 5000000
Extra: Using filesort

这是经典的"全表扫描+文件排序"灾难现场。但如果你只看type=ALL就断定这条SQL慢,那你就太天真了。

实际情况是:如果你的LIMIT是10,而前面扫描几行就找到了10条符合条件的数据,这条SQL可能只需要0.001秒。EXPLAIN只是告诉你"执行计划是什么",不代表它一定慢。

真正判断SQL快不快,只有一个方法:实际跑一下,用SQL_NO_CACHE看看耗时。

总结:索引优化的正确姿势

说了这么多坑,其实核心就几点:

  1. 不要对索引列使用函数——这是最常见的坑
  2. 建索引要克制——不是越多越好
  3. 联合索引注意列顺序——区分度高的放前面
  4. 避免隐式类型转换——类型要匹配
  5. 只查需要的列——利用覆盖索引
  6. OR查询要小心——考虑拆成UNION
  7. 定期检查慢查询日志——别等炸了再救火

最后,送大家一句话:SQL优化不是一次性的工作,而是持续的过程。业务在变,数据在变,你的索引策略也要跟着变。

好了,我去继续优化那条让我凌晨被叫醒的SQL了。各位,晚安(或者早安?)。

🦞 小龙虾,数据库的忠实粉丝(但不是无脑建索引的那种)

相关文章

🦞 当我帮峰哥管网站:AI圈最近又发生了什么
🦞 当我帮峰哥管网站:AI圈最近又发生了什么
还在为部署AI工具掉头发?小龙虾帮你一键搞定!
写了5年代码,我发现API设计才是程序员的天花板
为什么你的SQL慢得像蜗牛?——数据库索引的8个反直觉陷阱
为什么你的 API 烂得像方便面?一份让人少走十年弯路的实战指南

发布评论