UPDATE比INSERT慢是因为从库SQL Thread单线程回放时,UPDATE需先定位再修改并维护索引,而INSERT仅为追加写入;批量UPDATE易导致Seconds_Behind_Master陡升,同量级INSERT影响甚微。

主从同步里 UPDATE 比 INSERT 慢,不是因为语句本身“天生慢”,而是从库在回放 binlog 时,UPDATE 需要先定位、再修改、再维护索引和日志,而 INSERT 是追加写入——这个差异在主从单线程回放模型下会被显著放大。
UPDATE 回放必须走“查 + 改”两步,Insert 只是追加
MySQL 从库的 SQL Thread 是单线程顺序执行 relay log 中的事件。对 INSERT,只要表结构固定,就是简单插入新行(尤其用自增主键时,InnoDB 追加到叶子页末尾,IO 成本低);而 UPDATE 必须先根据 WHERE 条件找到目标行——这一步可能触发全表扫描(比如没索引)、二级索引回表、甚至 LOB 页读取(更新 TEXT 字段时)。更关键的是,即使只改一个字段,InnoDB 仍需重写整行(或溢出页),并同步更新所有涉及的二级索引。
- 常见错误现象:
Seconds_Behind_Master在批量UPDATE后陡升,但同量级INSERT几乎无感 - 使用场景:主库执行
UPDATE user SET status=1 WHERE created_at ,从库回放时若 <code>created_at无索引,就会扫全表 - 性能影响:一次
UPDATE的 I/O 和 CPU 开销通常是同量级INSERT的 3–10 倍,尤其在大表、多索引、含大字段时
Row 格式下 UPDATE 日志体积大,网络和磁盘压力翻倍
当 binlog_format = ROW(生产环境推荐),UPDATE 事件记录的是“变更前后的整行镜像”,而 INSERT 只记新增行。如果更新了 TEXT 或 BLOB 字段,单条 binlog 事件可能达几百 KB;主库写 binlog 快(顺序写),但从库要把它写进 relay log、再解析、再执行——磁盘随机读写 + 内存拷贝开销直接拉高延迟。
- 参数差异:
binlog_row_image = FULL(默认)会记录所有列,改成MINIMAL可减小日志体积,但要求主从表结构严格一致 - 容易踩的坑:误以为“主库没压力,从库就不该慢”,其实从库的瓶颈常在磁盘 I/O(特别是机械盘或低配云盘)而非 CPU
- 验证方法:用
mysqlbinlog --base64-output=DECODE-ROWS -v查看实际 binlog 事件大小,对比 INSERT/UPDATE 事件长度
锁等待和索引维护在从库被串行化放大
主库上并发的 UPDATE 可能分散在不同行、不同页,锁冲突少;但从库 SQL Thread 是单线程,所有 UPDATE 被强制串行执行——前一个 UPDATE 持有行锁或 MDL 锁(比如刚执行完 ALTER TABLE),后面的就得排队。更隐蔽的是:即使没有显式锁,InnoDB 更新导致的页分裂、缓冲池淘汰、redo log 刷盘也会相互干扰。
- 常见错误现象:从库
SHOW PROCESSLIST显示 SQL Thread 状态为Updating或Waiting for table metadata lock,持续数秒以上 - 使用场景:主库分批跑
UPDATE,但从库回放时因某条语句卡住(如更新无索引字段),后续全部阻塞 - 排查要点:检查
performance_schema.data_locks和SHOW ENGINE INNODB STATUS中的TRANSACTIONS部分,确认是否有未提交事务或长持有锁
真正难处理的不是单条 UPDATE 慢,而是它把主库的并发压力“折叠”成从库的串行瓶颈——索引缺失、大字段、ROW 格式、单线程回放,四者叠加时,延迟会指数级增长。别只盯着 Seconds_Behind_Master 数值,得看 relay log 积压速率和单条事件执行耗时。


















