alter table add index 会生成巨量 binlog,因为其底层逐行构建b+树索引,在row格式下每行插入均记为独立write_rows事件,500万行表可产生500万条binlog记录;即使inplace算法也强制记录行变更以保障复制安全。

为什么 ALTER TABLE ADD INDEX 会生成巨量 binlog
因为 MySQL 在 ALTER TABLE 创建索引时,底层会逐行读取原表数据、构建 B+ 树、写入新索引页——这个过程被视作「大量 DML 操作」,每行插入到辅助索引都可能触发 binlog 记录。尤其在 binlog_format=ROW 模式下,不是记一条 ALTER TABLE 语句,而是把每一行索引键值的插入动作都记成独立的 Write_rows_v2 事件。
- 例如一张 500 万行的表加普通二级索引,binlog 可能新增 500 万条
Write_rows事件,体积轻松超 GB - 即使使用
ALGORITHM=INPLACE(如 5.6+ 的ADD INDEX),只要 binlog 开启且格式为 ROW,MySQL 仍会记录行变更(这是复制安全性的强制要求) -
OPTIMIZE TABLE或REBUILD类操作同理,本质是重建聚簇索引+所有二级索引,binlog 量更恐怖
ROW 模式下 binlog 记录的是什么
不是 SQL,也不是“建了哪个索引”,而是具体到每一行索引键的物理写入行为。用 mysqlbinlog --verbose 解析后,你会看到类似:
### INSERT INTO `test`.`t1` ### SET ### @1=1001 /* INT meta=0 nullable=0 is_null=0 */ ### @2='abc' /* STRING(10) meta=10 nullable=0 is_null=0 */ ### @3=1715184000 /* TIMESTAMP(0) meta=0 nullable=0 is_null=0 */
这表示:向索引中插入了一行键值(@1,@2,@3 是索引列对应的字段值)。如果该索引包含 3 列,就记 3 个字段;如果表有 100 万行,就重复这段结构 100 万次。
- STATEMENT 模式下只记一行:
# INSERT INTO `test`.`t1` ...(实际不记,因为 DDL 不走 DML binlog 流程,但索引创建本身仍会触发 row 级日志) - ROW 模式无法跳过单行记录——哪怕你只是加一个空索引,MySQL 也必须扫描全表、确认每行都应出现在该索引中
- MIXED 模式在此类 DDL 场景下通常 fallback 到 ROW,所以不能依赖它“自动省日志”
如何避免建索引时 binlog 疯涨
核心思路:不让建索引操作走主库 binlog 复制路径,或压缩其影响范围。
- 若从库暂不需同步该索引,可临时关闭主库 binlog:
SET SESSION sql_log_bin = 0,再执行ALTER TABLE(注意:仅限单机或明确知道从库可手动补建的场景) - 对大表,优先用
pt-online-schema-change工具,它通过影子表+触发器方式增量同步,binlog 压力分散在长时间窗口内,而非集中爆发 - 确认
max_binlog_size设置合理(如512M),避免单个 binlog 文件过大导致备份/解析卡死 - 切勿在业务高峰期执行,否则 binlog 写放大 + 主从延迟 + 磁盘 IO 高峰会三重叠加
容易被忽略的关键点
很多人以为“建索引是 DDL,不会进 binlog”,其实错在混淆了两类日志:DDL 语句本身不进 binlog(如 CREATE INDEX 语句不会被复制),但建索引过程中引发的**数据行变更**一定会被记录——尤其在 ROW 模式下,这是不可绕过的复制一致性保障机制。真正决定 binlog 体积的,从来不是“你敲了什么命令”,而是“MySQL 底层做了多少行级写入”。











