题目:联合索引是越多越好吗?
难度:medium
标签:db/mysql/index, mysql/index, difficulty/medium
评分:⭐⭐⭐⭐☆(4/5)
首先明确一点,索引并不是越多越好,索引创建要符合业务需求,因为索引创建跟使用都是需要成本的。
每建立一个索引,都要在插入更新数据的时候进行索引更新,插入跟更新的数据,相应的效率就会降低。所以我们使用索引的时候,或者说使用联合索引,使用索引的时候,要控制我们索引的创建量。
在主业务中去考虑使用哪些索引,而不是所有字段都去建立索引。并且尽可能地选择区分度高或者是有价值的字段去增加设置索引。另外可以考虑联合索引方面的设计是在覆盖索引中使用,减少回表。通过覆盖索引来实现不做回表,一次性把所需的数据展现出来。但是如果涉及的字段太多,并且没有什么查询意义,可能就不考虑这方面的使用。
优点:
不足:
(a,b,c) 已存在时 (a,b)、(a) 是前缀冗余;治理手段(schema_unused_indexes、INVISIBLE INDEX)ALTER TABLE t ALTER INDEX idx INVISIBLE 灰度下线、FUNCTIONAL INDEX(表达式索引)、DESCENDING INDEX补充:
(a, b) 一个联合索引 vs (a) + (b) 两个单索引——联合索引空间更省、写放大更低;但单索引能覆盖更多查询模式SELECT a, b FROM t WHERE a=? AND b=? 走 (a, b) 索引是覆盖SELECT a, b, c FROM t WHERE a=? 走 (a, b, c) 是覆盖但索引宽度爆炸sys.schema_unused_indexes:找 count_star=0 的索引performance_schema.table_io_waits_summary_by_index_usage:精确统计INVISIBLE INDEX:灰度下线 1-2 周再删核心考点定位:考察索引设计中的成本收益权衡思维——不是能不能建,而是值不值得建。
知识点拆解:
(a, b) 一个联合索引:空间省一半、写放大减半、查询 WHERE a=? AND b=? 最优;但只覆盖以 a 打头的查询(a) + (b) 两个单列索引:能覆盖 WHERE a=? 和 WHERE b=? 两种独立模式;但空间翻倍、写放大翻倍EXPLAIN 显示 Using index(a, b, c) 存在时 (a, b)、(a) 冗余,因为 (a, b, c) 已能覆盖前缀查询(a) 和 (a) 重复(a, b) 和 (b, a) 都能用但用途不同,通常保留一个sys.schema_unused_indexes:SELECT * FROM sys.schema_unused_indexes 找 count_star=0performance_schema.table_io_waits_summary_by_index_usage:细粒度统计ALTER TABLE t ALTER INDEX idx INVISIBLE → 观察 1-2 周无 SQL 走这个索引 → DROP INDEXINVISIBLE INDEX:灰度下线首选FUNCTIONAL INDEX:表达式索引,如 INDEX((lower(email))),让 WHERE lower(email)=? 也能走DESCENDING INDEX:降序索引,让 ORDER BY a DESC 直接走索引考察方向:
延伸递进面试题:
(a, b, c) 已存在时,要不要再建 (a, b)?(a, b) 而不查 c,且 (a, b) 索引更小更省内存,可以保留;否则治理掉performance_schema.table_io_waits_summary_by_index_usage 看每个索引的 count_write;结合 SHOW INDEX FROM t 看索引列表;把低使用率的索引 INVISIBLE 灰度观察面试官您好,联合索引绝对不是越多越好,我按"三张账单 + 三个策略"来讲。
先说三张账单。第一是写放大,每次 INSERT/UPDATE/DELETE 都要维护所有索引的 B+ 树,5 个索引就是 5 倍维护成本。第二是空间,每个索引一份完整 B+ 树副本,索引多了不但磁盘爆,Buffer Pool 也被索引挤占,命中率下降。第三是优化器成本,候选方案多了优化器估算更慢,大表上 QPS 会有影响。这三笔账加起来,索引是奢侈品,不是必需品。
再说三个策略。第一个是联合索引 vs 单列索引的取舍——常用查询模式合并成联合索引(比如 WHERE a=? AND b=? 建 (a, b) 就够),独立的查询模式才用单列索引。避免把每个字段都单建。第二个是覆盖索引的边界——覆盖索引能省回表是好事,但为了覆盖把所有 SELECT 列都塞进一个索引,索引宽度会爆炸,反而挤压 Buffer Pool。经验值是索引宽度别超过数据行宽的一半。第三个是冗余治理,这块特别容易忽略——(a, b, c) 存在时 (a, b)、(a) 就是冗余,应该删掉。治理工具用 sys.schema_unused_indexes 找 count_star=0 的索引;MySQL 8.0 有 ALTER TABLE t ALTER INDEX idx INVISIBLE,先设为不可见观察 1-2 周,确认没有 SQL 依赖再真删。
最后是分场景策略。读多写少的报表库、维表可以多建;写多读少的订单写入、日志表要克制,能用分区和归档策略就少建索引;核心业务表要精细设计联合索引,冗余治理必须做。
8.0 还有几个特性值得知道:INVISIBLE INDEX 灰度下线、FUNCTIONAL INDEX 表达式建索引(INDEX((lower(email))) 让函数查询也能走)、DESCENDING INDEX 让 ORDER BY a DESC 直接走索引。
当然,我的经验是——索引不是能不能建的问题,而是值不值得建。业务有真实查询模式才建,没查询模式的索引都是浪费,删掉能省资源、少踩坑。