索引越多,INSERT/UPDATE/DELETE 越慢
每新增一条记录或修改带索引的字段时,MySQL 不仅要写数据页,还要同步更新所有相关索引的 B+ 树结构。这意味着:
INSERT每多一个二级索引,就要多一次随机磁盘写(或缓冲池刷脏);
UPDATE若修改了索引列,可能触发索引项删除 + 重建;
DELETE则需在每个索引中定位并移除对应条目。
常见错误现象包括:
批量导入 10 万行数据,加了 5 个二级索引后耗时从 2 秒涨到 47 秒UPDATE user SET status = 1 WHERE id = 123很快,但改成
UPDATE user SET email = 'x@y.z' WHERE id = 123后变慢——因为
哪些索引对写入影响最大
影响程度取决于索引类型、是否唯一、以及字段更新频率。排序从高到低:
UNIQUE索引:每次写入都必须做重复值校验,涉及索引查找 + 锁定范围,开销显著高于普通索引 长字符串字段上的索引(如
VARCHAR(500)):B+ 树节点存储更多字节,导致页分裂更频繁,写放大更严重 频繁更新的列上的索引(如
updated_at、
status):每次
UPDATE都触发索引维护 复合索引中靠前的列若常被更新,整条索引项都要挪动;靠后的列更新则影响小得多
如何在读写间做实际取舍
没有通用最优解,但可按以下原则快速判断:
先用EXPLAIN确认查询真正在用哪个索引,避免“以为有用其实没走”的假索引 对写入密集型表(如日志、消息队列),优先保留
PRIMARY KEY和极少数高频
WHERE条件索引,其余改用应用层缓存或异步聚合查询 批量写入前临时禁用非必要索引(仅限
MyISAM;
InnoDB不支持,但可用
DROP INDEX+
ADD INDEX替代,注意锁表时间) 用
innodb_change_buffering = all(默认)让插入/更新的二级索引变更先缓存在内存,减少随机 IO —— 但会增加缓冲池压力,且崩溃后需重放 change buffer
SHOW VARIABLES LIKE 'innodb_change_buffering';
容易被忽略的隐性成本
索引不仅拖慢写入,还会悄悄吃掉更多资源:
每个索引都占用ibdata1或独立表空间,备份体积和恢复时间线性增长
SELECT COUNT(*)在无
WHERE时,InnoDB 必须遍历主键或某个索引——如果只有大字段的二级索引,反而比全表扫还慢 很多 ORM 自动生成的“防错索引”(如给所有外键加索引)并无真实查询支撑,纯属冗余
ANALYZE TABLE会重新采样索引统计信息,索引越多,采样越久,期间可能阻塞 DML
真正需要权衡的从来不是“要不要索引”,而是“这个索引有没有被稳定、高频、不可替代地用到”。
