<p>索引列顺序必须按等值过滤列优先、范围查询列靠后、排序分组列最后排列,否则最左前缀匹配失效;select * 无法触发覆盖索引;对索引列使用函数或表达式会导致索引失效。</p>

索引列顺序写反了,WHERE a = ? AND b = ? 却建了 (b, a)
复合索引的列顺序不是随便排的。MySQL 用最左前缀匹配,(b, a) 索引对 WHERE a = ? 完全无效,连 WHERE b = ? AND a = ? 也只用上第一层。实际执行时可能走全表扫描。
实操建议:
- 按查询条件中等值过滤列优先、范围查询列靠后、排序/分组列最后的顺序建索引
-
WHERE tenant_id = ? AND status = ? ORDER BY created_at DESC→ 推荐索引:(tenant_id, status, created_at) - 用
EXPLAIN看key_len和Extra字段:如果key_len明显偏小,或出现Using filesort,大概率顺序不合理
SELECT * 搭配覆盖索引失效
覆盖索引要求查询字段全部被索引包含,但 SELECT * 会拉出所有列,哪怕你建了 (a, b, c) 索引,只要表里还有 d 字段,就无法触发覆盖,必然回表。
实操建议:
- 明确只查需要的字段,比如改
SELECT *为SELECT a, b, c - 若高频查询固定几列,可建联合索引并确保字段全覆盖,例如:
CREATE INDEX idx_cover ON t (a, b, c); - 注意
TEXT/BLOB类型不能建在索引中,即使声明了也会被忽略,导致你以为“覆盖”了其实没覆盖
在索引列上做函数或计算,WHERE YEAR(created_at) = 2024
只要对索引列用了函数、表达式、类型转换,索引就直接失效。MySQL 无法用 B+ 树快速定位 YEAR(created_at) 的结果,只能全扫。
实操建议:
- 把函数移到右边:
WHERE created_at >= '2024-01-01' AND created_at - 避免隐式类型转换:
WHERE user_id = '123'(user_id是INT)会触发转换,改成WHERE user_id = 123 - 字符串比较注意 collation,
utf8mb4_0900_as_cs和utf8mb4_general_ci混用可能导致索引跳过
过度索引:给每个 WHERE 字段都单独建索引
单列索引多而杂,不仅不加速,反而拖慢写入(每次 INSERT/UPDATE 都要维护多个 B+ 树),还可能让优化器选错执行计划。
实操建议:
- 优先合并:已有
(a, b)索引时,WHERE a = ?和WHERE a = ? AND b = ?都能用上,不必再建(a) - 删掉长期没用的索引:查
sys.schema_unused_indexes视图(MySQL 8.0+),或用performance_schema.table_io_waits_summary_by_index_usage - 注意前缀索引陷阱:
INDEX (email(16))可能导致WHERE email = 'x@y.z'匹配失败,除非所有邮箱前 16 字符都唯一
EXPLAIN、真实数据分布、QPS 和写入压力一起看,脱离场景谈索引,90% 是纸上谈兵。










