UPDATE时索引负责定位原记录、写undo log、更新聚簇索引,并同步更新所有相关二级索引的B+树节点,索引越多写放大越严重;DISABLE KEYS对InnoDB无效,真正有效的是临时删索引或改用LOAD DATA INFILE。

UPDATE 时索引到底在忙什么
每次 UPDATE 执行,InnoDB 不是只改一行数据,而是同步做四件事:定位原记录、写 undo log、更新聚簇索引(主键)、再逐个更新所有相关二级索引的 B+ 树节点。哪怕你只改一个 status 字段,只要它上有 idx_status,就得删旧键、插新键、分裂页、调整树高——这些全在事务内串行完成。
索引越多,写放大越严重。实测一千万行表更新两百万行,3 个低区分度二级索引就贡献了近 70% 的耗时。
DISABLE KEYS 对 InnoDB 完全无效
很多人看到 “禁用索引提速” 就跑 ALTER TABLE t DISABLE KEYS,但这个命令在 InnoDB 表上会被静默忽略(只对 MyISAM 生效)。你执行完查 SHOW INDEX,索引还在;看慢日志,索引更新照样发生。
真正有效的做法只有两个:
- 临时
DROP INDEX idx_col ON table_name(注意:不能删主键或外键依赖的索引) - 或改用
LOAD DATA INFILE替代UPDATE,它天然绕过 SQL 解析和大部分索引维护路径
删哪些索引最值得
不是所有索引都该留着。以下情况删掉后收益明显,且风险可控:
-
WHERE条件没用到的索引,比如仅用于后台报表ORDER BY create_time,但报表一周跑一次 - 字段区分度极低的索引,如
TINYINT status只有 0/1/2,B+ 树几乎退化成链表,维护成本远超查询收益 - 复合索引中靠后的被更新字段,例如
INDEX idx_user_id_status (user_id, status),你总在批量改status,会导致大量页分裂 - 冗余索引,如已有
(a,b),又单独建了(a),删后者即可
关键前提:WHERE 子句涉及的列必须仍有可用索引(比如主键或高频查询字段),否则删完索引,UPDATE 直接退化为全表扫描,速度不升反降。
重建索引比导入还慢?那得看怎么建
重建索引慢,往往是因为用 CREATE INDEX 在线建,它边读数据边构建 B+ 树,锁粒度大、I/O 高。更优解是:
- 导完数据后,用
ALTER TABLE t ENGINE=InnoDB触发隐式重建(需确保innodb_file_per_table=ON) - 或先
CREATE TABLE t_new LIKE t,建好所需索引,再INSERT INTO t_new SELECT * FROM t,最后原子替换 - 避免在业务高峰期重建,尤其当表上有唯一约束时,
CREATE INDEX会校验全量数据,可能卡住数分钟
最容易被忽略的一点:如果 UPDATE 本身没走索引(比如 WHERE id > 1000000 但 id 没索引),删任何二级索引都没用——此时第一要务是给 WHERE 字段补索引,而不是急着砍别的。


















