从源码到实战盘点MySQL中不写Binlog的N种场景
作者:Seal^_^
Binlog(二进制日志)是MySQL的核心组件,负责记录数据变更,支撑着主从复制、数据恢复等重要功能,本文将深入MySQL源码,彻底解析哪些场景下MySQL不会写入binlog,希望对大家有所帮助
引言
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_executed | GTID执行记录 |
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_TABLE | CREATE TEMPORARY TABLE |
| 系统日志表 | 操作mysql.general_log等 | table_category == TABLE_CATEGORY_LOG | 慢日志写入 |
| 复制信息表 | 操作mysql.slave_*表 | table_category == TABLE_CATEGORY_RPL_INFO | 复制状态更新 |
| GTID表 | 操作mysql.gtid_executed | table_category == TABLE_CATEGORY_GTID | GTID记录 |
| Performance Schema | 操作performance_schema表 | 存储引擎标志HA_NO_BINLOG | TRUNCATE |
| 会话级关闭 | SET sql_log_bin = 0 | OPTION_BIN_LOG标志 | 批量导入 |
| 从库不记录 | log_replica_updates=OFF | opt_log_replica_updates | 从链式复制 |
| 内部操作 | 实例初始化/升级 | Disable_binlog_guard | mysqld --initialize |
| 显式指定 | NO_WRITE_TO_BINLOG | lex->no_write_to_binlog | OPTIMIZE 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的场景的资料请关注脚本之家其它相关文章!
