增加索引会拖慢insert、update、delete操作,尤其在高频写入场景下,因需同步更新所有相关索引的b+树结构,增加i/o与锁竞争,并可能降低缓冲池命中率。

增加索引会拖慢哪些操作?
加索引不是免费的——它直接拖慢 INSERT、UPDATE、DELETE,尤其是高频写入场景。每新增一条记录,MySQL 不仅要写数据页,还要同步更新所有相关索引的 B+ 树结构;更新某列时,若该列在多个索引中存在,每个索引都要重排键值和指针。
常见误判是只看 SELECT 速度变快了就认为“稳赚不赔”,但线上表如果每秒写入上千行,一个冗余的 INDEX 可能让 INSERT 延迟翻倍,甚至触发锁等待或主从延迟。
- 单条
INSERT的开销 = 数据行写入 + 每个索引的叶节点分裂/插入(B+ 树维护成本) -
UPDATE影响范围取决于是否修改索引列:改非索引列,只更新聚簇索引;改索引列,所有含该列的索引都要更新 - 索引越多,缓冲池(
innodb_buffer_pool_size)压力越大,可能挤占热数据页,间接拉低整体查询命中率
怎么测新增索引的真实代价?
别只用 EXPLAIN 看执行计划——它不反映并发写入下的锁竞争和 I/O 压力。真实评估必须在类生产环境做压测,重点盯三个指标:QPS、平均响应时间、InnoDB 行锁等待次数。
推荐组合工具:用 sysbench 模拟混合负载(比如 70% 写 + 30% 读),对比加索引前后的 SHOW ENGINE INNODB STATUS\G 中的 Row lock waits 和 Row lock current waits;同时用 pt-query-digest 分析慢日志里写语句的 Query_time 分布变化。
- 测试前先清空
innodb_buffer_pool(重启或执行SET GLOBAL innodb_buffer_pool_dump_now=ON后再 reload)避免缓存干扰 - 写压测至少持续 5 分钟,避开初始预热抖动;观察
Threads_running是否持续高于 10 - 特别注意
ALTER TABLE ... ADD INDEX过程本身:MySQL 5.6+ 支持ALGORITHM=INPLACE,但仍会锁表(LOCK=NONE才真正无锁),大表操作务必在低峰期做
哪些索引大概率不值得加?
低选择性字段(如 status TINYINT 只有 0/1)、短文本前缀(VARCHAR(255) 只建 INDEX(col(4)))、或者只被 ORDER BY 用到却没 WHERE 条件的列,基本属于“看起来有用,实则浪费”。MySQL 优化器对这类索引往往直接忽略,但维护成本照旧。
- 判断选择性:执行
SELECT COUNT(DISTINCT col)/COUNT(*) FROM table;,结果低于 0.01 就谨慎考虑 - 联合索引顺序错位:比如经常查
WHERE a=1 AND b=2 ORDER BY c,却建了INDEX(a, c, b)——c在中间导致排序失效,实际无法避免Using filesort - 重复索引:已有
INDEX(a, b),再加INDEX(a)是冗余的;已有PRIMARY KEY(id),又建UNIQUE KEY(id)会多存一份索引数据
线上加索引如何最小化风险?
核心原则:不阻塞业务、可观测、可回滚。MySQL 原生 ALTER TABLE 在大表上仍可能卡住 DML,优先用 pt-online-schema-change 或 MySQL 8.0+ 的原子 DDL(需确认 innodb_defragment=OFF 且表无全文索引)。
- 执行前检查:
SELECT table_name, engine, row_format FROM information_schema.tables WHERE table_schema='db' AND table_name='t';—— 若是MyISAM或COMPRESSED行格式,pt-osc可能不兼容 - 监控关键信号:加索引期间紧盯
SHOW PROCESSLIST里是否有Waiting for table metadata lock;用innotop观察InnoDB Row Operations的写入速率是否骤降 - 回滚预案不是删索引(
DROP INDEX同样耗时),而是提前备份CREATE INDEX语句对应的ALTER TABLE ... DROP INDEX,并确保 binlog 格式为ROW,以便必要时闪回
最常被忽略的一点:索引统计信息不会实时更新。加完索引后立即跑 ANALYZE TABLE,否则优化器可能还在用旧的基数估算,导致执行计划反而变差。











