在 MySQL 的运维与开发中,binlog(二进制日志)是实现主从复制、数据恢复的核心组件。了解哪些操作不会被记录到 binlog 对于深入理解复制机制、排查数据一致性问题以及进行性能优化至关重要。本文将深入 MySQL 源码,系统剖析在 ROW 格式(当前最常用的格式)下,判断一个操作是否需要写入 binlog 的核心逻辑,并全面梳理导致 binlog 写入被“静默”的各类场景。
ROW 格式下判断操作是否写入 binlog 的实现逻辑
在 ROW 格式下,将数据行的变更记录到 binlog 的核心函数是 binlog_log_row。其简化逻辑如下:
int binlog_log_row(TABLE *table, const uchar *before_record,
const uchar *after_record, Log_func *log_func) {
bool error = false;
THD *const thd = table->in_use;
// 关键判断:当前操作是否需要写入 binlog
if (check_table_binlog_row_based(thd, table)) {
// ... 执行具体的binlog写入操作
}
return error ? HA_ERR_RBR_LOGGING_FAILED : 0;
}
函数首先调用 check_table_binlog_row_based 进行判断。若返回 true,则根据操作类型(INSERT/UPDATE/DELETE)调用相应的处理函数将行数据镜像写入 binlog。因此,我们的分析重点就在于 check_table_binlog_row_based 函数。
static bool check_table_binlog_row_based(THD *thd, TABLE *table) {
// ... (缓存检查逻辑,后文详述)
return (thd->is_current_stmt_binlog_format_row() &&
table->s->cached_row_logging_check &&
(thd->variables.option_bits & OPTION_BIN_LOG) &&
mysql_bin_log.is_open());
}
该函数返回 false(即不写binlog)的条件非常清晰,只需满足以下任意一项:
- 当前语句非ROW格式:
thd->is_current_stmt_binlog_format_row() 为 false(如DDL语句)。
- 表不允许复制:
table->s->cached_row_logging_check 为 false。
- 线程级binlog被关闭:
(thd->variables.option_bits & OPTION_BIN_LOG) 为 false。
- binlog文件未打开:
mysql_bin_log.is_open() 为 false。
条件1和4通常不构成常见例外,因此下文将重点解析条件2和条件3为 false 的各种具体场景,这也是理解 MySQL主从复制机制 异常行为的关键。
场景一:cached_row_logging_check 为 false
该标志的赋值逻辑如下,关键在于三个子条件的“与”关系:
int const check(table->s->tmp_table == NO_TMP_TABLE &&
!table->no_replicate &&
binlog_filter->db_ok(table->s->db.str));
1. 操作对象为临时表
若 table->s->tmp_table != NO_TMP_TABLE,即当前操作针对的是用户创建的临时表(CREATE TEMPORARY TABLE),则不会写入 binlog。这是为了隔离会话间的临时数据。
2. 库名被复制过滤器排除
如果库名不满足 --replicate-do-db、--replicate-ignore-db 等参数设置的复制规则,binlog_filter->db_ok() 将返回 false。这属于主动配置的复制过滤行为。
3. 表属性 no_replicate 被设置为 true
这是最核心且情况最复杂的部分。no_replicate 属性在 open_table_from_share() 函数中根据表的类别和存储引擎能力进行设置。
A. 特殊类别的系统表
以下类别的表天生就被标记为 no_replicate:
- TABLE_CATEGORY_LOG:包括
mysql.general_log 和 mysql.slow_log。这正是文章开篇问题的答案:对慢日志表的DML操作(如插入慢查询记录)不会写入 binlog,因此不会复制到从库或MGR节点。
- TABLE_CATEGORY_RPL_INFO:复制信息表,如
mysql.slave_master_info, mysql.slave_relay_log_info, mysql.slave_worker_info。
- TABLE_CATEGORY_GTID:如
mysql.gtid_executed。
B. 存储引擎能力声明
存储引擎通过标志位向Server层声明其复制支持能力。相关标志有:
HA_BINLOG_STMT_CAPABLE:支持STATEMENT格式。
HA_BINLOG_ROW_CAPABLE:支持ROW格式。
HA_HAS_OWN_BINLOGGING:引擎自行管理binlog(如NDB Cluster)。
no_replicate 在以下情况被设为 true:
outparam->no_replicate =
!(flags & (HA_BINLOG_STMT_CAPABLE | HA_BINLOG_ROW_CAPABLE)) ||
(flags & HA_HAS_OWN_BINLOGGING);
这意味着,如果一个存储引擎既不支持STATEMENT也不支持ROW格式,或者宣称自己管理binlog,那么对它的操作就不会被Server层的binlog记录。
在MySQL内置引擎中,performance_schema 和 temptable(用于内部临时表)存储引擎没有设置上述任一能力标志。因此,对所有 performance_schema 下表的DML操作都不会写入 binlog。这也是为什么即使用root用户,通常也无法直接对 performance_schema 中的表进行DML操作(会报权限错误),但部分表允许的 TRUNCATE 操作同样不写binlog。
场景二:OPTION_BIN_LOG 为 false
OPTION_BIN_LOG 是线程级变量 option_bits 中的一个位标志,控制当前会话的操作是否写入 binlog。
1. 会话级显式关闭
通过 SET SESSION sql_log_bin = 0; 命令,可以临时关闭当前会话的binlog记录。其回调函数会清除 OPTION_BIN_LOG 标志。
2. 从库SQL线程默认行为
当实例作为从库运行时,其SQL线程(或replica线程)默认不将重放的应用事件写入自身的binlog,除非显式开启了 log_replica_updates(或旧版的 log_slave_updates)参数。这主要是为了避免循环复制和节省空间。
3. 内部使用 Disable_binlog_guard
MySQL源码中广泛使用 Disable_binlog_guard 这个RAII守卫类,在特定代码块内临时关闭binlog记录,离开作用域后自动恢复。主要场景包括:
- 实例初始化 (
--initialize):创建系统表时。
- 实例升级:升级系统表结构时。
- 插件与组件管理:
INSTALL/UNINSTALL PLUGIN/COMPONENT。
- 服务器对象管理:
CREATE/ALTER/DROP SERVER。
- 内部维护操作:如
ALTER TABLE 过程中创建/删除中间临时表、DROP DATABASE 清理元数据、更新数据字典、后台自动更新列统计信息(直方图)等。这些操作是MySQL为了维护数据一致性和 数据库性能 而自动执行的。
附加场景:语句级控制 no_write_to_binlog
除了上述基于线程和表的控制,MySQL还支持在语句级别通过将 thd->lex->no_write_to_binlog 设置为 true 来避免写入binlog。
1. 特定的管理命令
如 SHUTDOWN、RESTART、RESET MASTER、RESET SLAVE、RESET PERSIST 等。
2. 显式关键字
部分维护类SQL支持 NO_WRITE_TO_BINLOG 或它的别名 LOCAL 关键字:
OPTIMIZE NO_WRITE_TO_BINLOG TABLE t1;
ANALYZE LOCAL TABLE t1;
REPAIR NO_WRITE_TO_BINLOG TABLE t1;
FLUSH LOCAL PRIVILEGES;
需要注意的是,某些 FLUSH 命令即使不指定关键字,默认也不写binlog,例如 FLUSH LOGS、FLUSH BINARY LOGS、FLUSH TABLES WITH READ LOCK。
总结与实践指导
综上所述,MySQL通过多层级的精细控制来决定是否将操作写入 binlog。尽管场景列举较多,但可以遵循一个核心原则来记忆:
凡是MySQL服务器内部自动执行的操作(非用户主动发起的DML/DDL),通常都不会写入binlog。
这包括了系统表的维护(如slow_log)、复制元信息更新、数据字典变更、后台统计信息收集等。反之,用户对普通表发起的DML操作,在未主动关闭binlog且无复制过滤的情况下,都会正常记录。
理解这些机制,有助于我们:
- 正确解读主从数据不一致问题,排除因系统表未复制导致的误判。
- 安全地进行实例初始化和升级,知晓这些操作不会影响复制链路。
- 在需要进行 大规模数据维护 时,合理使用
sql_log_bin 或 NO_WRITE_TO_BINLOG 关键字来提升效率并控制binlog体积。