SQL 单列索引与复合索引最佳实践

1次阅读

单列索引足够。where仅用一个字段时,建index(a)即可高效命中,无需提前建复合索引;复合索引虽能通过最左前缀匹配生效,但浪费空间和写入性能,且对where b = ?完全无效。

SQL 单列索引与复合索引最佳实践

WHERE 条件里只用一个字段,该建单列索引还是复合索引?

单列索引足够。mysqlpostgresql 等主流数据库WHERE a = ? 这类查询能高效命中 INDEX(a),不需要为了“以后可能加条件”提前建 INDEX(a, b)。复合索引在单字段查询时虽可能被用上(最左前缀匹配),但会浪费磁盘空间和写入开销,更新 b 字段时也要维护冗余索引项。

  • 单列索引体积小,B+ 树层级更低,点查更快
  • 复合索引 INDEX(a, b)WHERE a = ? 有效,但对 WHERE b = ? 完全无效
  • 如果业务明确只按 a 过滤,建 INDEX(a) 更干净;后续真要查 a AND b,再补 INDEX(a, b) 也不迟

WHERE 同时过滤两个字段,为什么 INDEX(a, b)INDEX(a) + INDEX(b) 快?

因为 MySQL 通常一次查询只用一个索引(5.7+ 的 index merge 是例外,但代价高、不可靠)。WHERE a = ? AND b = ? 用两个单列索引,优化器大概率退化为全表扫描或只选其一,再回表过滤另一个条件;而 INDEX(a, b) 能直接定位到唯一叶子节点,避免回表或二次过滤。

  • 复合索引的 B+ 树按 a 排序,a 相同时再按 b 排序,天然支持等值+等值联合查找
  • INDEX(a) + INDEX(b)AND 场景下几乎不协同工作,除非走 index merge(查看执行计划里是否出现 index_merge
  • 注意顺序:INDEX(a, b)WHERE a = ? AND b = ? 高效,但对 WHERE b = ? 无效

ORDER BY 和 WHERE 混用时,复合索引字段顺序怎么排?

优先满足 WHERE 的等值条件,再接 ORDER BY 字段。例如 WHERE status = 'active' ORDER BY created_at DESC,应建 INDEX(status, created_at)。这样索引能先快速定位所有 status = 'active' 的行,且这些行在索引中已按 created_at 排好序,无需额外排序。

  • 等值条件(=IN)可作为索引前导列;范围条件(>BETWEEN)之后的字段无法用于索引排序
  • WHERE a > 10 ORDER BY bINDEX(a, b) 时,b 部分不参与排序(因为 a 是范围扫描,b 在各 a 分组内有序,但跨组无序)
  • 如果 ORDER BY b DESC, c ASC,索引字段顺序必须严格对应,且方向需一致(MySQL 8.0+ 支持混合方向,但老版本只认全部 ASC 或全部 DESC)

建了复合索引,EXPLAIN 却显示没用上,常见原因有哪些?

最常见的是条件没触达最左前缀,或隐式类型转换导致索引失效。

  • WHERE b = ? 不会用 INDEX(a, b),哪怕 b 是第二列
  • 字段类型和参数类型不一致:比如 user_idBIGINT,但 SQL 里写成 WHERE user_id = '123'字符串),触发隐式转换,索引失效
  • 对索引字段用了函数:WHERE YEAR(created_at) = 2024 无法用 INDEX(created_at),应改写为 WHERE created_at >= '2024-01-01' AND created_at
  • 使用了 !=NOT INLIKE '%xxx' 等无法走索引的表达式

索引不是建了就万事大吉,每条查询都得看 EXPLAIN 输出里的 keyrows 字段——前者告诉你用了哪个索引,后者暴露实际扫描行数,比“是否用索引”更能说明问题。

text=ZqhQzanResources