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

 更新时间:2026年02月24日 09:14:01   作者: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_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的场景的资料请关注脚本之家其它相关文章!

相关文章

最新评论