☰
MySQL 索引失效的 7 个坑,我踩过 4 个:函数、隐式转换、左模糊、OR
2026/10/2 5:56:40 网站建设 项目流程

导读:慢查询日志天天报警,EXPLAIN 一看 type=ALL 全表扫。索引明明建了就是不生效,查了一周,把 MySQL 索引失效的场景踩了个遍。这篇把我踩过的 4 个坑和另外 3 个高频场景整理出来,每一条都带 SQL 和修复写法,建议收藏。

MySQL 索引失效的 7 个坑,我踩过 4 个:函数、隐式转换、左模糊、OR

先说最典型的场景。职位表 50 万行,按发布时间查:

SELECT*FROMjob_WHEREDATE(create_time)='2026-10-01';

create_time明明建了索引,EXPLAIN 却显示type=ALL,全表扫 50 万行。问题就出在DATE()函数把索引列包住了。

坑一:对索引列用函数,索引直接报废

-- 慢:DATE() 包住索引列,MySQL 无法用索引SELECT*FROMjob_WHEREDATE(create_time)='2026-10-01';-- 快:改成范围查询,索引正常生效SELECT*FROMjob_WHEREcreate_time>='2026-10-01 00:00:00'ANDcreate_time<'2026-10-02 00:00:00';

索引列上做任何函数运算、算术运算,索引都失效。这是最常见的坑,我栽过两次。

坑二:隐式转换,VARCHAR 列传数字

用户表phone是 VARCHAR,接口收到的是数字:

-- 慢:phone 是 VARCHAR,传数字触发隐式转换,索引失效SELECT*FROMuser_WHEREphone=13700001234;-- 快:带引号,类型匹配,索引生效SELECT*FROMuser_WHEREphone='13700001234';

MySQL 对字符串列和数字比较,会把字符串转成数字再比较,相当于对索引列做了函数转换。修复方案:代码层保证类型一致,或者查 SQL 看EXPLAIN的 key 是不是 null。

坑三:左模糊 LIKE,%在开头必失效

-- 慢:% 在开头,索引树没法定位SELECT*FROMcompany_WHEREnameLIKE'%科技%';-- 快:后缀匹配可以用索引(但实际业务很少这么查)SELECT*FROMcompany_WHEREnameLIKE'晴空%';

%keyword%这种中间模糊基本告别索引。搜索场景要么上 ES,要么用前缀匹配,别指望 MySQL 索引扛全量模糊搜索。我项目里职位搜索直接走 ES,MySQL 只做精确匹配。

坑四:OR 连接,一侧没索引全表扫

-- 慢:id 有索引,status 没有,OR 导致全表扫SELECT*FROMjob_WHEREid=123ORstatus=1;-- 快:拆成两个查询 UNION 合并SELECT*FROMjob_WHEREid=123UNIONSELECT*FROMjob_WHEREstatus=1;

OR 要两侧都能用索引才走索引合并,只要有一侧扫全表,整个查询就全表扫。要么给 status 补索引,要么拆 UNION。

坑五:联合索引没按最左前缀

建了idx_tenant_status (tenant_id, status),但查询只带 status:

-- 慢:跳过了最左列 tenant_id,联合索引用不上SELECT*FROMjob_WHEREstatus=1;-- 快:带上最左列SELECT*FROMjob_WHEREtenant_id=1ANDstatus=1;

联合索引最左前缀原则,查询条件必须从联合索引的第一列开始。中间跳过某一列,后面的列也失效。

坑六:对索引列做运算

-- 慢:对列做运算SELECT*FROMjob_WHEREsalary*1.1>10000;-- 快:运算放到常量侧SELECT*FROMjob_WHEREsalary>10000/1.1;

列在运算左侧,索引失效。把运算挪到等号/不等号右侧,让索引列保持裸列。

坑七:NOT IN / IS NOT NULL,选择性太低

-- 慢:NOT IN 优化器评估走索引不如全表快SELECT*FROMjob_WHEREstatusNOTIN(2,3);-- 慢:IS NOT NULL 选择性低SELECT*FROMjob_WHEREremarkISNOTNULL;

这类条件优化器经常选择全表扫。能给默认值别留 NULL,表设计时把可空字段尽量NOT NULL DEFAULT '',省一堆麻烦。

踩坑:索引建了不生效,字符集不一致

现象:职位表和公司表 JOIN 查公司名,company_id两边都建了索引,EXPLAIN 却是全表扫 + 驱动表都不对。

排查过程:SHOW CREATE TABLE对比两张表,一个 utf8 一个 utf8mb4。JOIN 时字符集不同,MySQL 要先转码再比较,索引失效。

定位思路:JOIN 两边的列类型、字符集、排序规则必须一致,否则索引用不上。

最终解决:统一改成 utf8mb4:

ALTERTABLEcompany_CONVERTTOCHARACTERSETutf8mb4COLLATEutf8mb4_unicode_ci;

改完 EXPLAIN 变成type=ref,从 8 秒降到 30ms。

例外:数据量小的时候,优化器可能"故意"不走索引

别一看到 type=ALL 就觉得索引废了。表里总共 500 行数据,全表扫比走索引快(索引要两次 IO:索引树 + 回表),优化器直接选全表扫,这是正常行为。

判断标准很简单:数据量上来(几十万行)还 type=ALL,那才是真失效。用EXPLAIN看 rows 估算值,和数据总量对比,如果rows接近总量,大概率索引没生效或选择性太低。

可直接复用的要点

  • 索引列禁止函数、运算、隐式转换,保持"裸列"。
  • VARCHAR 列查询永远带引号;JOIN 两列类型、字符集、排序规则一致。
  • %关键词%搜索别指望 MySQL 索引,上 ES。
  • OR 两侧都要有索引,否则拆 UNION。
  • 联合索引遵循最左前缀,字段顺序按查询频次排。
  • 表设计少留 NULL,能 DEFAULT 就 DEFAULT。
  • 写完 SQL 养成习惯:EXPLAIN 看一眼 key 和 type,type 不是 ref/range 就重写。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询