TW

// post

MySQL 索引失效的 8 个真实场景

建了索引却走了全表扫描,是实践中最常见也最令人困惑的问题。本文整理 8 个高频场景与对应的改写方式。

2026.06.10·3 min read·1754 views

前提:先看执行计划

讨论"索引失效"前,先确认你的判断依据是 EXPLAIN 的输出,而不是感觉。

EXPLAIN SELECT * FROM t_order WHERE user_id = 1001 AND status = 1;

关注三列:type(访问类型)、key(实际使用的索引)、rows(预估扫描行数)。

场景一:联合索引未遵循最左前缀

索引 idx(a, b, c),以下查询无法用到索引:

WHERE b = 1 AND c = 2        -- 跳过 a

改写:调整索引顺序或补上最左列。

场景二:在索引列上做运算

WHERE YEAR(created_at) = 2026         -- 索引失效
WHERE created_at >= '2026-01-01' AND created_at < '2027-01-01'   -- 走索引

原则:让函数作用在常量上,而不是列上。

场景三:隐式类型转换

-- phone 是 varchar,传入数字会触发全表转换
WHERE phone = 13800000000
WHERE phone = '13800000000'   -- 正确写法

场景四:LIKE 以通配符开头

WHERE name LIKE '%探微%'      -- 无法使用索引
WHERE name LIKE '探微%'       -- 可以使用索引

改写:前缀模糊场景考虑全文索引或搜索引擎。

场景五:OR 连接非索引列

WHERE user_id = 1 OR remark = 'x'   -- remark 无索引,整体退化

改写:拆成 UNION 两部分。

场景六:范围条件之后的列用不上索引

索引 (a, b),查询 WHERE a > 10 AND b = 2。a 的范围查询之后,b 无法用于定位,只能用于覆盖。

改写:若 b 选择性更高,考虑调整索引顺序。

场景七:区分度太低的列单独建索引

性别、状态这类只有几个值的列,单独建索引往往不如全表扫描。

INDEX(status)              -- 选择性差,优化器可能直接放弃
INDEX(status, created_at)  -- 组合起来才有价值

场景八:N+1 查询伪装成性能问题

有时慢的不是单条 SQL,而是被调用了一千次:

for (Order o : orders) {
    detailMapper.selectByOrderId(o.getId());   // 1000 次查询
}

改写:批量查询后在内存分组,把 1001 次压成 2 次。

收尾建议

优化前先测量,优化后要复测。养成用 EXPLAIN 验证的习惯,比记住一堆结论更可靠。

本文为 探微编程原创,转载请注明出处

// contact

有需求?先聊聊你的项目

不管是一次技术支持、一个完整项目,还是想补一段工程能力,都可以先沟通。我们会先了解需求并给出判断,再谈方案与报价。

wechat
Trdfyl
qq
2356245930
探微编程 微信二维码

扫码添加微信