从源码到实战盘点MySQL中不写Binlog的N种场景

来源:这里教程网 时间:2026-02-24 11:13:43 作者:
引言1. Binlog写入的核心判断逻辑1.1 判断逻辑流程图1.2 check_table_binlog_row_based源码解析2. 场景一:临时表操作2.1 现象演示2.2 源码验证2.3 为什么临时表不写binlog?3. 场景二:特殊系统表3.1 不写binlog的系统表列表3.2 现象演示3.3 源码分析4. 场景三:Performance Schema表4.1 现象演示4.2 存储引擎能力标志5. 场景四:显式关闭binlog5.1 会话级关闭5.2 源码回调函数5.3 典型应用场景6. 场景五:从库未开启log_replica_updates6.1 配置说明6.2 源码逻辑6. 场景六:临时关闭binlog的代码保护机制6.1 Disable_binlog_guard类6.2 应用场景7. 场景七:NO_WRITE_TO_BINLOG关键字7.1 支持的SQL命令7.2 源码实现7.3 默认不写binlog的FLUSH命令8. 场景八:performance_schema的TRUNCATE操作8.1 现象8.2 原因分析9. 总结与对比表格9.1 所有场景汇总9.2 快速判断指南10. 最佳实践建议10.1 日常开发注意事项10.2 复制环境配置建议结语

引言

Binlog(二进制日志)是MySQL的核心组件,负责记录数据变更,支撑着主从复制、数据恢复等重要功能。但你是否遇到过这样的疑问:

为什么mysql.slow_log表的变更不会同步到从库?SET sql_log_bin = 0到底做了什么?performance_schema的数据变化会记录到binlog吗?

本文将深入MySQL源码,彻底解析哪些场景下MySQL不会写入binlog,并通过源码级别的分析,让你不仅知其然,更知其所以然。

1. Binlog写入的核心判断逻辑

在MySQL源码中,binlog写入的入口函数是binlog_log_row

// sql/binlog.cc int binlog_log_row(TABLE *table, const uchar *before_record, const uchar *after_record, Log_func *log_func) { THD *const thd = table->in_use; // 核心判断:当前操作是否需要写入binlog if (check_table_binlog_row_based(thd, table)) { // 写入表映射信息 if (!write_locked_table_maps(thd)) { // 根据操作类型调用不同的日志函数 error = (*log_func)(thd, table, has_trans, before_record, after_record); } } return error ? HA_ERR_RBR_LOGGING_FAILED : 0; }

1.1 判断逻辑流程图

1.2 check_table_binlog_row_based源码解析

static bool check_table_binlog_row_based(THD *thd, TABLE *table) { // 缓存判断结果,避免重复计算 if (table->s->cached_row_logging_check == -1) { int const check( table->s->tmp_table == NO_TMP_TABLE && // 不是临时表 !table->no_replicate && // 允许复制 binlog_filter->db_ok(table->s->db.str) // 库在复制白名单 ); table->s->cached_row_logging_check = check; } // 需要满足4个条件才返回true return (thd->is_current_stmt_binlog_format_row() && // 行格式 table->s->cached_row_logging_check && // 表允许复制 (thd->variables.option_bits & OPTION_BIN_LOG) && // 线程启用binlog mysql_bin_log.is_open()); // binlog已打开 }

结论:只要上述4个条件任意一个不满足,操作就不会写入binlog。

2. 场景一:临时表操作

2.1 现象演示

-- 创建临时表 CREATE TEMPORARY TABLE temp_user ( id INT PRIMARY KEY, name VARCHAR(50) ); -- 插入数据(不会写入binlog) INSERT INTO temp_user VALUES (1, '张三'); -- 更新数据(不会写入binlog) UPDATE temp_user SET name = '李四' WHERE id = 1;

2.2 源码验证

// table->s->tmp_table == NO_TMP_TABLE 为false时,cached_row_logging_check为0 int const check( table->s->tmp_table == NO_TMP_TABLE && // 临时表时,此条件为false !table->no_replicate && binlog_filter->db_ok(table->s->db.str) );

2.3 为什么临时表不写binlog?

根本原因:临时表的生命周期仅限于创建它的会话,对其他会话(包括从库)不可见,因此无需同步。

3. 场景二:特殊系统表

3.1 不写binlog的系统表列表

表类别具体表名作用日志表mysql.general_log通用查询日志日志表mysql.slow_log慢查询日志复制信息表mysql.slave_relay_log_info从库中继日志信息复制信息表mysql.slave_master_info从库主库连接信息复制信息表mysql.slave_worker_info从库并行复制信息GTID表mysql.gtid_executedGTID执行记录

3.2 现象演示

-- 开启慢日志记录到表 SET GLOBAL log_output = 'TABLE'; SET GLOBAL slow_query_log = ON; -- 执行慢查询 SELECT SLEEP(2); -- 查看慢日志表(写入成功) SELECT * FROM mysql.slow_log; -- 但这些记录不会同步到从库!

3.3 源码分析

// sql/table.cc - open_table_from_share函数 if ((share->table_category == TABLE_CATEGORY_LOG) || (share->table_category == TABLE_CATEGORY_RPL_INFO) || (share->table_category == TABLE_CATEGORY_GTID)) { outparam->no_replicate = true; // 这些表设置no_replicate标志 }

表类别定义

// sql/table.h enum enum_table_category { TABLE_CATEGORY_USER, // 用户表 TABLE_CATEGORY_LOG, // 日志表 TABLE_CATEGORY_RPL_INFO, // 复制信息表 TABLE_CATEGORY_GTID, // GTID表 TABLE_CATEGORY_INFORMATION_SCHEMA, // 信息模式 TABLE_CATEGORY_PERFORMANCE_SCHEMA // 性能模式 };

4. 场景三:Performance Schema表

4.1 现象演示

-- 尝试修改performance_schema表(会报错) UPDATE performance_schema.accounts SET CURRENT_CONNECTIONS = 10; -- ERROR: 1044 (42000): Access denied for user -- 但可以truncate(不会写入binlog) TRUNCATE performance_schema.accounts; -- Query OK, 0 rows affected

4.2 存储引擎能力标志

// 在Perfschema存储引擎的table_flags函数中 handler::Table_flags ha_perfschema::table_flags() const { return HA_REC_NOT_IN_SEQ | HA_NO_TRANSACTIONS | HA_NO_BINLOG; // HA_NO_BINLOG表示不支持binlog }

判断逻辑

else if (outparam->file) { const handler::Table_flags flags = outparam->file->ha_table_flags(); outparam->no_replicate = !(flags & (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE)) || (flags & HA_HAS_OWN_BINLOGGING); }

5. 场景四:显式关闭binlog

5.1 会话级关闭

-- 关闭当前会话的binlog SET SESSION sql_log_bin = 0; -- 以下操作不会写入binlog INSERT INTO user VALUES (100, '测试用户'); UPDATE user SET name = '新名字' WHERE id = 100; -- 重新开启 SET SESSION sql_log_bin = 1;

5.2 源码回调函数

static bool fix_sql_log_bin_after_update(sys_var *, THD *thd, enum_var_type type) { if (thd->variables.sql_log_bin) thd->variables.option_bits |= OPTION_BIN_LOG; // 开启 else thd->variables.option_bits &= ~OPTION_BIN_LOG; // 关闭 return false; }

5.3 典型应用场景

-- 场景1:批量导入大量数据,避免binlog暴涨 SET SESSION sql_log_bin = 0; LOAD DATA INFILE '/data/big_data.csv' INTO TABLE history_data; SET SESSION sql_log_bin = 1; -- 场景2:从库配置不记录重放日志 -- 在从库上设置 log_replica_updates = OFF -- 这样从库SQL线程重放的操作不写入自己的binlog

6. 场景五:从库未开启log_replica_updates

6.1 配置说明

# my.cnf [mysqld] # MySQL 8.0之前 log_slave_updates = OFF # MySQL 8.0+ log_replica_updates = OFF

6.2 源码逻辑

void set_slave_thread_options(THD *thd) { ulonglong options = thd->variables.option_bits | OPTION_BIG_SELECTS; if (opt_log_replica_updates) options |= OPTION_BIN_LOG; // 开启:从库操作记录binlog else options &= ~OPTION_BIN_LOG; // 关闭:从库操作不记录binlog }

6. 场景六:临时关闭binlog的代码保护机制

6.1 Disable_binlog_guard类

MySQL在源码中使用RAII机制临时关闭binlog:

class Disable_binlog_guard { public: explicit Disable_binlog_guard(THD *thd) : m_thd(thd), m_binlog_disabled(thd->variables.option_bits & OPTION_BIN_LOG) { // 构造函数中关闭binlog thd->variables.option_bits &= ~OPTION_BIN_LOG; } ~Disable_binlog_guard() { // 析构函数中恢复 if (m_binlog_disabled) m_thd->variables.option_bits |= OPTION_BIN_LOG; } private: THD *const m_thd; const bool m_binlog_disabled; };

6.2 应用场景

场景1:实例初始化(–initialize)

static bool handle_bootstrap_impl(handle_bootstrap_args *args) { if (opt_initialize) { const Disable_binlog_guard disable_binlog(thd); // 初始化系统表,这些操作不写入binlog rc = process_iterator(thd, &comp_iter, true); } }

场景2:实例升级

bool upgrade_system_schemas(THD *thd) { Disable_binlog_guard disable_binlog(thd); // 升级系统表,不写入binlog err = fix_mysql_tables(thd) || fix_sys_schema(thd); }

7. 场景七:NO_WRITE_TO_BINLOG关键字

7.1 支持的SQL命令

-- 1. OPTIMIZE TABLE OPTIMIZE NO_WRITE_TO_BINLOG TABLE t1; OPTIMIZE LOCAL TABLE t1; -- LOCAL是NO_WRITE_TO_BINLOG的同义词 -- 2. ANALYZE TABLE ANALYZE NO_WRITE_TO_BINLOG TABLE t1; -- 3. REPAIR TABLE REPAIR NO_WRITE_TO_BINLOG TABLE t1; -- 4. FLUSH命令 FLUSH NO_WRITE_TO_BINLOG PRIVILEGES; FLUSH LOCAL TABLES; -- LOCAL版本

7.2 源码实现

// sql/sql_yacc.yy - 语法解析部分 opt_no_write_to_binlog: /* Empty */ { $$ = false; } | NO_WRITE_TO_BINLOG_SYM { $$ = true; } | LOCAL_SYM { $$ = true; } ; // 在语法规则中设置 repair_table_stmt: REPAIR opt_no_write_to_binlog TABLE ... { Lex->sql_command = SQLCOM_REPAIR; Lex->no_write_to_binlog = $2; // 设置no_write_to_binlog标志 }

7.3 默认不写binlog的FLUSH命令

-- 以下FLUSH命令即使不加NO_WRITE_TO_BINLOG,也不会写入binlog FLUSH LOGS; FLUSH BINARY LOGS; FLUSH TABLES WITH READ LOCK; FLUSH TABLES tbl_name ... FOR EXPORT;

8. 场景八:performance_schema的TRUNCATE操作

8.1 现象

-- performance_schema表支持TRUNCATE TRUNCATE performance_schema.events_statements_summary_by_digest; -- 但不会写入binlog -- 从库不会执行这个TRUNCATE操作

8.2 原因分析

// storage/perfschema/ha_perfschema.cc int ha_perfschema::truncate() { // 直接清空内存数据结构 reset_table_handles(m_table); return 0; // 不调用binlog_log_row,不写入binlog }

9. 总结与对比表格

9.1 所有场景汇总

场景触发条件判断依据典型示例临时表操作临时表tmp_table != NO_TMP_TABLECREATE TEMPORARY TABLE系统日志表操作mysql.general_log等table_category == TABLE_CATEGORY_LOG慢日志写入复制信息表操作mysql.slave_*表table_category == TABLE_CATEGORY_RPL_INFO复制状态更新GTID表操作mysql.gtid_executedtable_category == TABLE_CATEGORY_GTIDGTID记录Performance Schema操作performance_schema表存储引擎标志HA_NO_BINLOGTRUNCATE会话级关闭SET sql_log_bin = 0OPTION_BIN_LOG标志批量导入从库不记录log_replica_updates=OFFopt_log_replica_updates从链式复制内部操作实例初始化/升级Disable_binlog_guardmysqld --initialize显式指定NO_WRITE_TO_BINLOGlex->no_write_to_binlogOPTIMIZE LOCAL TABLE

9.2 快速判断指南

10. 最佳实践建议

10.1 日常开发注意事项

-- 1. 不要期望慢日志表被复制 -- ❌ 错误想法:在从库查询慢日志 SELECT * FROM mysql.slow_log; -- 从库可能是空的 -- ✅ 正确做法:在主库查询或通过监控系统收集 -- 2. 批量操作记得关闭binlog -- 大批量数据导入 SET SESSION sql_log_bin = 0; -- 执行批量操作 SET SESSION sql_log_bin = 1; -- 3. 维护操作使用LOCAL关键字 OPTIMIZE LOCAL TABLE large_table; -- 不写binlog,减少网络传输

10.2 复制环境配置建议

# 从库配置:如果不是链式复制,建议关闭 [mysqld] log_replica_updates = OFF relay_log = relay-bin read_only = ON

结语

通过源码级别的分析,我们可以看到MySQL对binlog写入有着精细的控制:

内部系统表:避免循环复制和性能开销临时对象:遵循会话隔离原则控制开关:提供灵活的管理手段RAII保护:确保关键操作的安全性

理解这些场景,不仅能帮助你更好地理解MySQL的运行机制,还能在实际运维中做出更合理的选择。

以上就是从源码到实战盘点MySQL中不写Binlog的N种场景的详细内容,更多关于MySQL不写Binlog的场景的资料请关注其它相关文章!

相关推荐