索引被用了不代表查询快,关键看实际扫描行数、回表次数和过滤效果;explain中key非空但rows接近总行数,表明是全索引扫描而非高效查找,需结合type(如index/all)、extra(如using where)及选择性综合判断。

索引被用了,不代表查询就快——关键看它实际扫了多少行、回了多少次表、是否真的过滤掉了数据。
EXPLAIN 显示 key 不为 NULL,但 rows 高得离谱
这是最常见也最容易误判的情况。优化器显示“用了索引”,可能只是从索引树头开始一路扫到尾(全索引扫描),比如 SELECT a FROM t 或 SELECT * FROM t WHERE id > 0。这类查询虽然走了索引结构,但没做有效过滤。
-
rows值接近或等于表总行数 → 实际是全索引扫描,不是高效查找 - 检查
type字段:index(全索引扫描)比range/ref慢得多,ALL更糟 - 即使
key显示有值,也要结合rows和Extra(如Using where是否出现)综合判断
索引选择性差,查出来一堆数据还得回表
在低区分度字段(如 gender、status)上建索引,MySQL 找到匹配的索引项后,仍要根据主键一个个回表取整行数据。回表次数越多,I/O 越高,尤其当 rows 达几十万时,性能断崖式下跌。
- 用
COUNT(DISTINCT column_name)/COUNT(*)算选择性,低于 0.1 就别单独建索引 - 联合索引里把高选择性列放在左边,否则右边列基本白搭
- 如果只查索引列本身(如
SELECT name FROM users WHERE name LIKE '张%'),可避免回表 —— 这叫覆盖索引
SQL 写法触发隐式转换或函数操作
哪怕索引存在,只要 WHERE 条件里对索引列做了任何运算或类型干扰,索引就直接失效。MySQL 不会帮你“猜”怎么用索引。
-
WHERE create_time > DATE_SUB(NOW(), INTERVAL 7 DAY)→ 函数作用于索引列,失效 -
WHERE mobile = 13812345678→mobile是字符串类型,隐式转数字导致索引失效 -
WHERE name LIKE '%三'→ 左模糊,无法用 B+ 树快速定位 - 改写建议:
create_time >= '2026-08-05'、mobile = '13812345678'、name LIKE '三%'
大字段 + SELECT * 导致网络和内存开销爆炸
索引再好,也救不了你把 10MB 的 TEXT 字段和 50 个其他字段一起 SELECT * 回来。尤其分页场景(LIMIT 100000, 20),MySQL 得先查出 100020 行,再丢掉前 100000 行 —— 扫描行数和传输量都失控。
- 永远显式列出需要的字段,避开
BLOB/TEXT/JSON类型列 - 深度分页不用
LIMIT offset, size,改用游标分页(如WHERE id > last_seen_id ORDER BY id LIMIT 20) - 确认是否真需要
ORDER BY:无索引的排序会触发Using filesort,加内存临时表
真正卡住性能的,往往不是“有没有索引”,而是“索引有没有真正减少扫描和回表”。每条慢 SQL 都得拿 EXPLAIN 对着 rows、type、Extra 逐项抠,而不是只看 key 是否为空。










