为什么 WHERE
条件用了索引,但执行计划还是走全表扫描?
常见错觉是“只要字段有索引,查询就一定用上”。实际 MySQL 优化器会评估
WHERE条件的 选择性(selectivity):如果数据分布极度倾斜(比如
status字段 95% 是
'active'),即使该字段有索引,优化器也可能判定走索引+回表的成本高于直接全表扫描。 用
SELECT COUNT(*) FROM t WHERE status = 'active';和
SELECT COUNT(*) FROM t;对比,估算选择性是否低于 5%~10% 执行
EXPLAIN后重点看
type列:若为
ALL或
index,说明未命中有效索引;再看
rows是否接近总行数 对低选择性字段,单独建索引意义不大;可考虑与高选择性字段组合成联合索引,把低选择性字段放在后面
联合索引中字段顺序怎么排才不白建?
联合索引
(a, b, c)实际只等价于三个索引:
(a)、
(a,b)、
(a,b,c),并不支持跳过前导列的查询(如
WHERE b = ?或
WHERE b = ? AND c = ?)。 把 过滤性最强 的字段放最左:比如用户表中
user_id(唯一)比
city(重复多)更适合做首列 把常用于
ORDER BY或
GROUP BY的字段靠右放置,可避免
Using filesort;例如查询
WHERE category = ? ORDER BY created_at DESC,索引应为
(category, created_at)注意
IN和范围查询(
>,
BETWEEN)会截断后续字段的索引使用:在
(a, b, c)上执行
WHERE a = 1 AND b > 10 AND c = 5,只有
a和
b能用索引,
c不会生效
DISTINCT
、JOIN
和 LIKE
查询怎么避开索引失效?
这三类操作极易触发索引退化,不是语法写错,而是语义导致无法利用 B+ 树结构。
DISTINCT在无
WHERE时通常需要扫描全部索引或数据页;若仅需去重某几列,确保这些列本身构成覆盖索引,例如
SELECT DISTINCT a, b FROM t可用
(a, b)索引避免回表
JOIN中被驱动表(即
EXPLAIN中
type为
ref/
eq_ref的那张)的关联字段必须有索引;且类型要严格一致——
VARCHAR(50)关联
VARCHAR(100)可能因隐式转换失效
LIKE以通配符开头(
LIKE '%abc')必然无法使用索引;若必须前缀模糊,考虑全文索引(
FULLTEXT)或倒排表;
LIKE 'abc%'是安全的,前提是字段没参与函数运算,如
UPPER(name) LIKE 'ABC%'就会失效
什么时候该删掉旧索引?
索引不是越多越好。冗余或长期未被使用的索引会拖慢写入性能,并增加优化器决策负担。
用sys.schema_unused_indexes视图(MySQL 5.7+)查哪些索引从没被
SELECT使用过;注意它不统计
INSERT/UPDATE/DELETE的维护开销 检查是否有重复索引:比如已有
(a, b),又建了
(a, b, c),前者在大多数场景下是冗余的(除非
c经常参与排序或覆盖查询) 定期用
SHOW INDEX FROM t结合慢查询日志反查,确认每个索引是否真实支撑了高频查询;一个索引三个月内没出现在任何
EXPLAIN输出里,大概率可以下线
SELECT object_name AS table_name, index_name, rows_selected, select_latency FROM sys.schema_index_statistics WHERE table_schema = 'your_db' ORDER BY select_latency DESC LIMIT 5;索引设计本质是权衡:它服务于具体查询模式,而非数据本身。很多“优化”失败,是因为盯着单条 SQL 的
EXPLAIN看,却忽略了业务中查询的分布、并发压力、以及写入频次对索引维护成本的真实影响。
