b+树高度随索引字段增加而上升,因固定页大小下宽记录减少每页容量,迫使树增层以容纳数据,导致i/o增多、性能下降。

因为B+树的页内键值密度直接决定树高,字段越多、单条索引记录越宽,每页能存的键就越少,树高被迫上升——查询一次要多走几层节点,I/O次数翻倍,性能就崩了。
为什么字段增加会让B+树变“高”
B+树每个节点(页)大小固定,默认16KB。所有索引列的值 + 主键值 + 指针 + 元数据都要塞进这一页里。字段一多,单条索引记录体积就涨,一页能装的记录数直线下降。为了容纳全部数据,树只能靠增加层数(高度)来扩展——从2层变成3层、4层,每次查询就要多读1–2个页。
-
SHOW VARIABLES LIKE 'innodb_page_size'查当前页大小,多数是16384(即16KB) - 一个
VARCHAR(255)字段在utf8mb4下最多占 1020 字节(255×4),两个这样的字段就逼近767字节前缀限制 - 联合索引列顺序不合理时,前面列区分度低(比如
status只有 0/1),后面高区分度列(如user_id)根本用不上,等于白占空间
16列上限不是拍脑袋定的
MySQL对单个索引最多允许16个列,这是InnoDB硬编码的限制,写死在源码里。超过就报错:Too many columns in index。它背后对应的是B+树内部结构对“键描述符”的内存分配上限——每个列都要维护偏移、类型、长度等元信息,堆叠太多会溢出缓冲区。
- 建表时用
KEY idx_multi (a,b,c,...)显式声明联合索引,列数超16直接失败 - 生成的执行计划里如果看到
Using index condition但key_len异常大(比如 >2000),说明索引太宽,可能已触发页分裂频繁 - 不要为“以后可能用到”提前加列,B+树不支持运行时动态裁剪字段;删列必须重建索引
字段多 ≠ 覆盖全场景,反而让优化器放弃走索引
优化器评估索引成本时,会估算“回表代价”。如果联合索引包含8个字段,但查询只用其中3个且非最左前缀,MySQL大概率判定:不如全表扫描。尤其当 WHERE 条件含范围查询(>、LIKE 'abc%')时,右边字段全部失效,宽索引反而成累赘。
- 错误示例:
INDEX(a,b,c,d),查询WHERE b = 1 AND c > 10——a没出现,整个索引无法使用 - 正确拆分思路:把高频等值查询字段放最左(如
tenant_id、status),范围字段放右,避免中间断层 -
EXPLAIN FORMAT=TREE能直观看到优化器是否真正用到了全部索引列
真正卡住索引扩展的从来不是语法上限,而是B+树对局部性原理的刚性依赖:它必须把相关键紧凑存放在连续页中。字段一多,紧凑性就碎,树就胖,硬盘寻道时间就涨——这个物理事实,比任何配置参数都难绕开。











