前言
好久不见。
断更挺长时间了,说出来都不好意思。
这两个月啊,真的是。团队走了几个老人,又来了一堆新人。业务方向也一直在变,今天说往东,明天又往西了。每天忙成狗,晚上回家躺床上就睡着了,连刷手机的力气都没有。
最近才算缓过来点。
昨天面试了个小伙子,我问他索引啥时候会失效,他想了半天,说了个LIKE的情况。我说还有呢?他就卡住了。其实他不是不知道,就是没系统地想过这个问题。
后来我翻了翻自己的笔记,想想不如整理一下发出来。也不求多深入,就是把常见的坑列一列。你要是准备面试,看完这篇,至少能答得有条理点。
至于为什么会这样,背后的原理啥的,咱们后面再聊。

一、对索引列用函数或做计算
先说第一个,我自己就踩过。
去年有个需求,要查2024年注册的用户。我当时偷懒,直接写了个 YEAR(create_time) = 2024
。看着没问题吧?结果查了半天,转圈圈转了快一分钟。
我还纳闷呢,create_time 明明建了索引的啊。
后来 EXPLAIN 看了一眼,好家伙,全表扫描。
看个对比你就懂了:
sql
-- ❌ 这样写,索引废了
SELECT * FROM users WHERE YEAR(create_time) = 2024;
SELECT * FROM orders WHERE amount + 10 > 1000;
-- ✅ 得这么写
SELECT * FROM users WHERE create_time >= '2024-01-01' AND create_time < '2025-01-01';
SELECT * FROM orders WHERE amount > 990;
为啥?你想啊,你对索引列做函数处理,数据库怎么用索引?它得把每条数据拿出来,先算个YEAR(),再跟2024比。那不就是挨个遍历嘛。
所以记住,别动索引列。
要算,你在右边算。
二、类型没对上
这个更坑,不太容易发现。
大概的意思可以看这个:
sql
-- phone 是 VARCHAR 类型
-- ❌ 完蛋,没加引号
SELECT * FROM users WHERE id = 13800138000;
-- ✅ 得加引号
SELECT * FROM users WHERE phone = '13800138000';
就差一对引号。
MySQL看到你拿数字去跟字符串比,它会自动转换。把phone转成数字再比。转换就相当于对phone做了函数操作,跟上面说的一样。
当然这个例子比较牵强,意会即可。
不管怎么说,对待类型得细心。
三、LIKE 前面带了百分号
这个知道的人很多,但还是有人会踩坑。
做搜索时,产品说要支持模糊搜索,搜啥都能搜出来。开发一听,那简单,LIKE '%关键词%'
走起。
结果呢?数据量大起来后就卡爆了。
sql
-- ❌ 这样写,索引白建
SELECT * FROM products WHERE name LIKE '%手机%';
SELECT * FROM products WHERE name LIKE '%iPhone';
-- ✅ 只有这样才能走索引
SELECT * FROM products WHERE name LIKE 'iPhone%';
你想啊,索引就跟字典一样,按顺序排的。你要找"iPhone开头"的,好办,直接翻到I那页。但你要找"包含iPhone"的,那它怎么办?只能从A翻到Z,每个都看一遍。
这样是不是就很好理解了。
所以遇到这种需求,跟产品商量商量,能不能只支持前缀搜。
实在不行,考虑上ES吧。
四、用了 OR
OR 看着挺正常的,实际上有坑。
有一回写查询,要找名字叫张三的,或者年龄是25的。我就很自然地写了个OR。跑起来慢得要死。
sql
-- 假设 name 有索引,age 没有
-- ❌ 全表扫描了
SELECT * FROM users WHERE name = '张三' OR age = 25;
-- ✅ 两个字段都得有索引
SELECT * FROM users WHERE name = '张三' OR email = 'test@example.com';
问题在哪呢?name有索引,age没有。MySQL一看,OR两边都得查,一个能走索引,一个不能。它想了想,算了,干脆全表扫吧,省事。
所以用OR得小心点。
五、联合索引没从左边开始
面试爱问这个。
假设你建了个联合索引,(name, age, city)。看下面这几个查询:
sql
-- 假设有联合索引 (name, age, city)
-- ✅ 这些可以
SELECT * FROM users WHERE name = '张三';
SELECT * FROM users WHERE name = '张三' AND age = 25;
-- ❌ 这些不行
SELECT * FROM users WHERE age = 25;
SELECT * FROM users WHERE city = '北京';
为啥?
联合索引得从最左边那个字段开始用,不能跳。还是上面查字典的例子,或者就跟图书馆找书一样,你得先找大分类,再找小分类,最后找具体的书。
肯定不能直接跑去找书名吧?那不还得把整个馆翻一遍。
面试被问到联合索引,十有八九会问这个。记住就行。
六、用了 NOT、!= 这些
否定条件,基本走不了索引。
sql
-- ❌ 没戏
SELECT * FROM users WHERE status != 1;
SELECT * FROM users WHERE age NOT IN (18, 19, 20);
你想啊,"不是1",那是2?3?4?还是其他的?范围太大了。MySQL算了算,还不如直接全表扫。
能的话,改成正向的。比如status就3个值,那我直接查 IN (2, 3)
不就得了。
七、IS NULL 这种
这个得看情况。
sql
-- ❌ 不一定走索引
SELECT * FROM users WHERE email IS NULL;
SELECT * FROM users WHERE email IS NOT NULL;
表里大部分email都是空的,你查IS NULL,那不是要扫一大片?MySQL一看,算了,全表扫吧。
反过来也一样。
所以建表的时候,尽量别让字段为空。给个默认值,省事。
八、SELECT *
这个不算失效,但影响性能。
我刚工作那会儿,老大看我写SQL,上来就说:别用星号。
我当时还不服气,用星号多方便啊。后来才懂:
sql
-- ❌ 得回表查所有字段
SELECT * FROM users WHERE name = '张三';
-- ✅ 只查需要的,快
SELECT id, name FROM users WHERE name = '张三';
你查的字段都在索引里,数据库直接从索引拿给你,不用回表。这叫覆盖索引。
但你一用星号,它得回表把所有字段都查出来。多一次IO,数据量大了,差距就明显了。
所以,用啥查啥。别偷懒。
九、数据太少
有时候挺奇怪的。测试环境跑得好好的,走索引。一到生产环境就不走了。
可能不是代码问题,是数据量。
表里就几千条数据,MySQL想了想,全扫一遍也就几毫秒,还折腾啥索引树啊。直接不用了。
这个没办法,等数据量上去就好了。所以测性能,得用真实的数据量。不然测了也白测。
十、索引重复率太高
还有一种。
比如性别字段,就俩值,男和女。你建了索引,查"性别=男",一半数据都是男的。MySQL一算,这索引有啥用?还不如全扫。
所以建索引前,看看重复率。太高的话,建了也没用。
写在最后
这两个月过得有点魔幻。
团队走了一拨人,来了一拨人。业务方向改来改去,今天这么干,明天那么干。每天忙得跟陀螺似的,回头一看,好像啥也没干成。
那感觉吧,就跟索引失效了一样。明明在跑,就是不出活。
现在好点了,坐下来整理整理,也算给自己个交代。
其实写代码跟做人差不多。建了索引,不一定能跑得快。方向不对,优化器再聪明也没用。人生可能也一样,选错路了,再怎么努力都白搭。
希望咱们都能找到自己的最左前缀吧。
好了,不早了,今天就这样。
下次见。
