结论:高并发写入下索引导致INSERT卡在updating状态,本质是每条INSERT需同步更新多个B+树且强制fsync redo,引发IO瓶颈;优化应优先删无用索引、控制刷盘频率与并发量。

直接说结论:高并发写入下索引带来的IO压力,本质是“每条INSERT要同步更新多个B+树 + 强制fsync redo”,不是索引本身慢,而是它把IO瓶颈一次性点爆了。优化方向必须是“减索引、控刷盘、压并发”,而不是硬扛。
为什么加索引会让INSERT变卡在updating状态?
你看到的连接卡在 updating 状态,背后是两个机制同时生效:
- InnoDB 必须为每条新记录同步更新所有二级索引,B+树插入 + 页分裂开销随索引数量非线性增长
-
innodb_flush_log_at_trx_commit=1(默认)强制每次COMMIT都fsyncredo 日志,建索引期间DML密集,IO瓶颈被彻底暴露
尤其当表已有5个以上二级索引时,单条写入的IO放大效应会非常明显——这不是参数能“调”出来的,是结构决定的。
删掉不用的索引比调大innodb_log_file_size更有效
很多人一上来就改日志文件大小或buffer pool,但真正容易被忽略的是:大量索引根本没人用。MySQL 5.7没内置 sys.schema_unused_indexes(那是8.0+才有的),得靠自己查:
SELECT object_schema, object_name, index_name FROM performance_schema.table_io_waits_summary_by_index_usage WHERE index_name IS NOT NULL AND count_star = 0 ORDER BY object_schema, object_name;
执行前确保已开启相关instrument:UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'wait/io/table%';
常见误建索引场景:
- 只在
WHERE中用status,却建了(status, created_at, id)联合索引,但实际查询从不带created_at - 对
JSON字段建了普通索引,但应用层始终用JSON_EXTRACT解析后过滤 - 冗余唯一索引:主键已是唯一,又对同一字段加
UNIQUE
写多读少的表,优先用普通索引而非唯一索引
高并发写入时,唯一索引会强制做唯一性校验,可能触发额外的change buffer合并或直接读页——尤其当二级索引页不在buffer pool中时,随机IO陡增。
实操建议:
- 如果业务层已保证唯一性(比如发号器生成ID),就别在数据库层再加
UNIQUE约束 - 对写入热点字段(如
user_id、order_status)建索引,优先选普通索引;唯一性由应用兜底 - 避免在写入频繁的字段上建函数索引(如
INDEX idx_upper_name ((UPPER(name)))),MySQL 5.7不支持函数索引,实际是无效的
innodb_flush_log_at_trx_commit设为2时,必须同步调大innodb_log_buffer_size
设为2能明显缓解IO压力,但有个关键前提:log buffer要够大,否则仍会频繁刷盘。默认 innodb_log_buffer_size=1M 在高并发写入下极易成为瓶颈。
调整建议:
- 先观察
SHOW GLOBAL STATUS LIKE 'Innodb_log_waits';,值 > 0 就说明buffer太小 - 按写入强度估算:若平均每事务写入2KB日志、QPS为1000,则buffer至少需
2KB × 1000 = 2MB,建议起步设为4M - 同时增大
innodb_log_file_size到1G(需停机操作),否则checkpoint太频繁,反而抵消buffer收益
注意:innodb_flush_log_at_trx_commit=2 意味着崩溃可能丢失1秒内事务,仅适用于可接受该风险的场景。
真正卡点从来不在“加索引”动作本身,而在它把原本就存在的IO争抢、锁等待、日志空间不足全暴露出来。先砍掉无用索引,再控住写入节奏,最后才动参数——顺序错了,调一天也白忙。


















