mysql写入变慢应优先检查innodb_flush_log_at_trx_commit和sync_binlog:设为2和0/1000可大幅提升吞吐,但需权衡数据一致性;批量插入用多值insert而非单行循环;避免on duplicate key update高并发锁争用;通过show engine innodb status分析semaphores和transactions定位内部瓶颈。

写入变慢时先看 innodb_flush_log_at_trx_commit 和 sync_binlog
MySQL 写入卡顿,尤其是高并发 INSERT/UPDATE 场景下,大概率不是磁盘慢,而是日志刷盘策略太保守。默认配置下,innodb_flush_log_at_trx_commit=1 要求每次事务都刷 redo log 到磁盘,sync_binlog=1 同样强制每次提交都同步 binlog——这在 SSD 上也扛不住每秒几千次小事务。
实操建议:
-
innodb_flush_log_at_trx_commit=2:日志写入 OS 缓冲即返回,崩溃可能丢失 1 秒数据,但写入吞吐能翻倍以上 -
sync_binlog=0或sync_binlog=1000:关闭或批量同步 binlog,主从延迟会略增,但避免频繁 fsync - 两个参数必须协同调整,单独改一个效果有限,且要确认业务能接受对应的数据一致性折衷
大批量写入别用单条 INSERT,改用 INSERT ... VALUES (),(),()
循环执行 INSERT INTO t VALUES (1),(2) 这种单行语句,在高并发下会迅速打满连接线程、锁竞争加剧、网络往返开销爆炸。即使开了 prepared statement,也难逃协议层和解析层的重复消耗。
实操建议:
- 把 100–1000 行合并成一条多值
INSERT,例如INSERT INTO t (a,b) VALUES (1,2),(3,4),(5,6) - 控制单条语句长度,避免超过
max_allowed_packet(默认 64MB),建议单批不超过 1MB - 配合
LOAD DATA INFILE更快,但需文件路径在 MySQL 服务端,且权限受限;客户端批量插入优先走多值 INSERT
INSERT ... ON DUPLICATE KEY UPDATE 在并发冲突时容易锁表
用 INSERT ... ON DUPLICATE KEY UPDATE 做“存在则更新、不存在则插入”很常见,但在高并发下,如果唯一键冲突频繁,InnoDB 会升级为间隙锁(gap lock)甚至 next-key lock,导致大量等待甚至死锁,表现就是写入线程卡在 update 状态。
实操建议:
- 确认唯一索引是否必要——有时加冗余唯一约束只为防重,反而成了瓶颈,可考虑用应用层幂等或分布式锁替代
- 若必须用,确保冲突率低(比如用户注册场景冲突极少),否则换成先
SELECT再INSERT/UPDATE,并用SELECT ... FOR UPDATE显式加锁,粒度更可控 - 注意
INSERT ... ON DUPLICATE KEY UPDATE对非冲突行也会触发自增 ID 分配,可能造成 ID 空洞,影响监控判断
写入瓶颈未必在 MySQL 本身,先查 show engine innodb status\G 里的 SEMAPHORES 和 TRANSACTIONS
有时候调了所有参数、换了 SSD、关了双写,写入还是上不去。这时候问题往往不在 SQL 或配置,而在 InnoDB 内部资源争用:比如 buffer pool mutex 争抢、log system latch 拥塞、或者长事务阻塞 purge 线程。
实操建议:
- 执行
show engine innodb status\G,重点看SEMAPHORES部分是否有持续 > 10 的 wait array slot 占用;TRANSACTIONS里是否有长时间未提交事务 - 检查
innodb_buffer_pool_instances是否过小(默认 1),高并发下设为 CPU 核数的 1–2 倍能缓解 mutex 争抢 - 观察
Innodb_data_fsyncs和Innodb_os_log_fsyncs的每秒次数,若远高于 QPS,说明刷盘太频繁,印证前面两个参数该调
真正卡住的时候,日志和状态输出比猜配置更有指向性。很多人直接调 innodb_log_file_size,却没发现其实是 purge lag 导致 history list 太长,反过来拖慢 insert。











