索引数量无硬上限,但物理开销会爆炸:每增一个索引即多一棵b+树,写入需同步更新所有索引,引发页分裂、i/o激增与锁竞争;索引过多还导致优化器误判、冗余干扰及767字节长度物理限制。

索引数量本身没有硬上限,但物理开销会爆炸
MySQL 5.7 对单个表的索引数量没有明确的数字限制(比如“最多64个”),information_schema.STATISTICS 里能存几百个索引名也完全合法。真正卡住你的不是“数量”,而是每个索引带来的磁盘、内存和 CPU 成本叠加后不可承受。
每加一个索引,InnoDB 就多一棵 B+ 树;每插入一行,就要同步更新所有相关索引树——包括页分裂、指针重连、缓冲区刷脏。5 个索引 = 5 倍写入路径,不是线性慢,是 I/O 和锁竞争指数级上升。
- 唯一索引额外触发全索引扫描做重复校验,比普通索引更重
- 长字段(如
VARCHAR(255)+utf8mb4)会让索引页快速膨胀,分裂更频繁 - 低区分度字段(如
status)放在联合索引最左,树高陡增,写入路径变长
索引太多会让优化器“选花眼”甚至误判
MySQL 5.7 的查询优化器在生成执行计划时,要评估所有可用索引的成本。索引越多,候选集越大,统计信息误差影响越明显——它可能放弃真正高效的索引,转而选择一个 type=ALL 的全表扫描方案,尤其当 ANALYZE TABLE 没及时更新时。
更隐蔽的问题是:冗余索引(比如已有 (user_id, status, created_at),又单独建了 (user_id))不会被自动合并或忽略,它们照常参与成本估算,徒增干扰项。
- 查
sys.schema_unused_indexes(MySQL 8.0+)不适用,5.7 得靠人工比对EXPLAIN日志和information_schema.STATISTICS -
EXPLAIN FORMAT=JSON中的used_key_parts和key_length才是判断是否真生效的关键证据
大表上删索引比建索引风险更高
DROP INDEX 在 MySQL 5.7 默认走 INPLACE,看起来轻量,但实际隐患更大:它不锁表,却直接移除物理结构。一旦应用某条关键查询(比如带 ORDER BY + LIMIT 的分页)原本强依赖这个索引,就会从毫秒级退化到秒级甚至超时。
还有更隐蔽的逻辑崩坏:有些业务用 DUPLICATE KEY 异常做流程分支,删掉唯一索引后,这个异常不再抛出,逻辑直接跳过。
- 务必先在从库或影子库上跑完整业务链路,确认无报错、无性能倒退
- 删之前导出当前索引定义:
SHOW CREATE TABLE t\G,留作回滚依据 - 避免在高峰期操作,
pt-online-schema-change的--critical-load参数必须设严(如Threads_running=500)
767 字节索引长度限制是物理墙
MySQL 5.7 默认 innodb_large_prefix=OFF,二级索引单列前缀最大只能 767 字节。这意味着:
-
VARCHAR(191)+utf8mb4刚好卡在边界(191 × 4 = 764),再加 1 就报Error 1071 - 联合索引各列字节相加超限,比如
(email, name)都是VARCHAR(255),直接失败 - 改配置(
innodb_file_format=Barracuda+ROW_FORMAT=DYNAMIC)能放开到 3072 字节,但需重建表,且旧备份/从库兼容性需验证
这堵墙不是设计缺陷,而是 InnoDB 页结构(16KB 固定大小)对索引项密度的硬约束——索引列越宽,一页能存的键值越少,树就越深,查询和写入都更慢。











