MySQL 在哪些场景下不会写 binlog

背景

在 MySQL 中,慢日志不仅可以记录在文件中,还可以记录在表中。具体是记录在文件还是表中是由log_output参数决定的。

该参数默认为FILE,即慢日志默认会记录在文件中。如果参数中包含TABLE,则慢日志还会记录在mysql.slow_log中,而mysql.slow_log使用的是 CSV 存储引擎。

最初研究这一问题,是为了确认在主从复制以及组复制(MGR)环境下,mysql.slow_log表中的慢日志是否会同步到其他节点。

随着分析的深入,发现 MySQL 实际上提供了多种机制和开关,用于确保操作不会写入 binlog。

由于 ROW 格式 是目前最常用的 binlog 格式,本文将从 ROW 模式下 MySQL 判断操作是否写入 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)) {
    ...
    if (likely(!(error = write_locked_table_maps(thd)))) {
      boolconst has_trans = thd->lex->sql_command == SQLCOM_CREATE_TABLE ||
                             table->file->has_transactions();
      // 根据操作类型,将行镜像写入 binlog
      error = (*log_func)(thd, table, has_trans, before_record, after_record);
    }
  }


                              return error ? HA_ERR_RBR_LOGGING_FAILED : 0;
}

首先调用 check_table_binlog_row_based 判断当前操作是否需要写入 binlog,若需要,则会针对不同的操作类型,调用不同的函数来处理。具体来说:

  • INSERT:Write_rows_log_event::binlog_row_logging_function。
  • UPDATE:Update_rows_log_event::binlog_row_logging_function。
  • DELETE:Delete_rows_log_event::binlog_row_logging_function。

接下来,重点看看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;
  }

  assert(table->s->cached_row_logging_check == 0 ||
         table->s->cached_row_logging_check == 1);


                              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,只需满足以下任意一个条件:

  • 当前 SQL 语句不能以 ROW 格式记录到 binlog 中:thd->is_current_stmt_binlog_format_row()为 false,例如 DDL 语句。
  • 表不允许写入 binlog:table->s->cached_row_logging_check为 false。
  • 当前线程未启用 binlog:thd->variables.option_bits & OPTION_BIN_LOG为 false。
  • binlog 未打开:mysql_bin_log.is_open() 为 false。

因为第一个条件和第四个条件为 false 的情况并不常见,下面将重点分析table->s->cached_row_logging_check和thd->variables.option_bits & OPTION_BIN_LOG为 false 时的场景。

cached_row_logging_check 为 false 的场景

table->s->cached_row_logging_check的赋值逻辑如下:

复制代码
  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;
  }

要使其为 false,必须满足以下任意一个条件:

  1. 当前表是临时表: table->s->tmp_table == NO_TMP_TABLE为 false。
  2. 库名不满足 --replicate-do-db、--replicate-ignore-db 复制规则:binlog_filter->db_ok(table->s->db.str)为 false。
  3. 表设置了 no_replicate。该属性是在open_table_from_share()函数中根据表的类型和存储引擎能力标志设置的。

no_replicate 的设置逻辑如下:

复制代码
  if ((share->table_category == TABLE_CATEGORY_LOG) ||
      (share->table_category == TABLE_CATEGORY_RPL_INFO) ||
      (share->table_category == TABLE_CATEGORY_GTID)) {
    outparam->no_replicate = true;
  } 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);
  } else {
    outparam->no_replicate = false;
  }

可以看到,no_replicate 会在以下几种情况设置为 true。

一、特殊类别的表。包括:

  • TABLE_CATEGORY_LOG 类别的表,具体包括 mysql.general_log, mysql.slow_log。
  • TABLE_CATEGORY_RPL_INFO 类别的表,具体包括 mysql.slave_relay_log_info,mysql.slave_master_info,mysql.slave_worker_info。
  • TABLE_CATEGORY_GTID 类别的表,具体包括 mysql.gtid_executed。

二、根据存储引擎的能力标志判断。

这些标志是每个存储引擎单独设置的,一般是在m_int_table_flags或table_flags函数中定义的,主要是用来向 Server 层声明:这个存储引擎的表,支持哪些能力/约束。与复制相关的标志有三个:

  • HA_BINLOG_STMT_CAPABLE:支持 STATEMENT 格式 binlog。
  • HA_BINLOG_ROW_CAPABLE:支持 ROW 格式 binlog
  • HA_HAS_OWN_BINLOGGING:该引擎自己管理 binlog(如 NDB Cluster)。

在 MySQL 支持的存储引擎中,只有 perfschema(对应 performance_schema)和 temptable(MySQL 8.0 引入的内部临时表存储引擎,主要用来替代老的 MEMORY/MyISAM 内部临时表)不会设置 HA_BINLOG_STMT_CAPABLE 或 HA_BINLOG_ROW_CAPABLE。

所以,针对 performance_schema 表的操作不会写入 binlog。

复制代码
`# ls mysql-8.4.3/storage/`
archive  blackhole  csv  example  federated  heap  innobase  myisam  myisammrg  ndb  perfschema  secondary_engine_mock  temptable

OPTION_BIN_LOG 为 false 的场景

thd->variables保存当前线程的会话级系统变量状态。其中,option_bits 是一个位图(bitmap),用于记录多个线程级选项标志,OPTION_BIN_LOG 则表示是否将当前线程的操作写入 binlog。

以下是几种典型场景。

一、显式关闭会话级 binlog

复制代码
`SET` SESSION sql_log_bin = 0;

该参数对应的回调函数是fix_sql_log_bin_after_update。

当sql_log_bin = 1时,打开 OPTION_BIN_LOG,反之,则清除 OPTION_BIN_LOG。

复制代码
static bool fix_sql_log_bin_after_update(sys_var *, THD *thd,
                                         enum_var_type type [[maybe_unused]]) {
  assert(type == OPT_SESSION);

  if (thd->variables.sql_log_bin)
    thd->variables.option_bits |= OPTION_BIN_LOG;
  else
    thd->variables.option_bits &= ~OPTION_BIN_LOG;

  return false;
}

二、从库未启用 log_replica_updates

当实例作为从库运行,且未开启 log_replica_updates 时,从库 SQL 线程重放的操作默认不写 binlog。

复制代码
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;
  else
    options &= ~OPTION_BIN_LOG;
  ...
}

三、使用Disable_binlog_guard临时关闭 binlog

Disable_binlog_guard用于在特定代码块内临时关闭 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) {
    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;

                              constbool m_binlog_disabled;
};

Disable_binlog_guard 被调用的场景有:

3.1 实例初始化(--initialize)

复制代码
static bool handle_bootstrap_impl(handle_bootstrap_args *args) {
  ...

              if (opt_initialize) {
    assert(thd->system_thread == SYSTEM_THREAD_SERVER_INITIALIZE);

    sysd::notify("STATUS=Initialization of MySQL system tables in progress\n");
    
    const Disable_binlog_guard disable_binlog(thd);
    const Disable_sql_log_bin_guard disable_sql_log_bin(thd);

    Compiled_in_command_iterator comp_iter;
    rc = process_iterator(thd, &comp_iter, true);

    thd->system_thread = SYSTEM_THREAD_INIT_FILE;

    sysd::notify("STATUS=Initialization of MySQL system tables ",
                 rc ? "unsuccessful" : "successful", "\n");

    if (rc != 0) {
      returntrue;
    }
  }
  ...

                              returnfalse;
}

3.2 实例升级

复制代码
bool upgrade_system_schemas(THD *thd) {
Disable_autocommit_guard autocommit_guard(thd);
  Bootstrap_error_handler bootstrap_error_handler;

Server_option_guard<bool> acl_guard(&opt_noacl, true);
Server_option_guard<bool> general_log_guard(&opt_general_log, false);
Server_option_guard<bool> slow_log_guard(&opt_slow_log, false);
Disable_binlog_guard disable_binlog(thd);
Disable_sql_log_bin_guard disable_sql_log_bin(thd);
  ...
  bootstrap_error_handler.set_log_error(false);

                              bool err =
      fix_mysql_tables(thd) || fix_sys_schema(thd) || upgrade_help_tables(thd);

                              if (!err) {
    /*
      Initialize structures necessary for federated server from mysql.servers
      table.
    */
    servers_init(thd);
    err = (DBUG_EVALUATE_IF("force_fix_user_schemas", true,
                            dd::bootstrap::DD_bootstrap_ctx::instance()
                                .is_server_upgrade_from_before(
                                    bootstrap::SERVER_VERSION_80011))
               ? check.check_all_schemas(thd)
               : check.check_system_schemas(thd)) ||
          check.repair_tables(thd) ||
          dd::tables::DD_properties::instance().set(
              thd, "MYSQLD_VERSION_UPGRADED", MYSQL_VERSION_ID);
  }
  ...

                              return dd::end_transaction(thd, err);
}

3.3 CREATE SERVER, ALTER SERVER 和 DROP SERVER 操作。

3.4 INSTALL COMPONENT, UNINSTALL COMPONENT 操作。

3.5 INSTALL PLUGIN, UNINSTALL PLUGIN 操作。

3.6 一些内部操作,例如 ALTER TABLE 过程中创建/删除临时表、DROP DATABASE 时清理数据库对象、更新数据字典表、后台线程自动更新列直方图。

除了上面介绍的这些场景,通过将 thd->lex->no_write_to_binlog 设置为true(thd->lex表示当前 SQL 语句的语法解析上下文),可以在语句级别控制该语句不写入 binlog。

NO_WRITE_TO_BINLOG 为 true 的场景

以下场景会将 no_write_to_binlog 设置为 true。

  1. SHUTDOWN、RESTART 命令。
  2. RESET 系列命令,包括:RESET MASTER, RESET SLAVE, RESET PERSIST。
  3. 显式指定NO_WRITE_TO_BINLOG或LOCAL。部分维护类 SQL 命令(OPTIMIZE, ANALYZE, REPAIR, FLUSH)支持在语句中显式指定不写 binlog,如,
复制代码
`OPTIMIZE` NO_WRITE_TO_BINLOG TABLE t1;

       ANALYZE LOCAL TABLE t1;

           REPAIR NO_WRITE_TO_BINLOG TABLE t1;

               FLUSH LOCAL PRIVILEGES;

需要注意的是,对于FLUSH命令,即使未显式指定NO_WRITE_TO_BINLOG,以下命令默认也不会写入 binlog:NO_WRITE_TO_BINLOG,FLUSH LOGS、FLUSH BINARY LOGS、FLUSH TABLES WITH READ LOCK、FLUSH TABLES tbl_name ... FOR EXPORT。

总结

虽然上面列举的场景较多,但实际上并不需要大家刻意去记。

简单来说,

  • 凡是 MySQL 内部自动执行的操作(即非用户手动执行的操作),通常不会写入 binlog。 典型场景包括:实例初始化与升级、mysql.slow_log表的写入、数据字典的维护、performance_schema表数据的更新等。

  • 对 mysql 库下的表进行 DML 操作,只要不属于上面提到的特殊类别的表,基本都会写入 binlog。

    但若执行的是 DDL 操作(如 truncate),基本都会写入 binlog。

  • 对 performance_schema 中的表进行 DML、DDL 操作会提示权限不足,即便是用 root 用户执行。但部分表允许执行 truncate 操作,且 truncate 操作不会写入 binlog。

相关推荐
Fleshy数模1 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
az44yao2 小时前
mysql 创建事件 每天17点执行一个存储过程
mysql
秦老师Q3 小时前
php入门教程(超详细,一篇就够了!!!)
开发语言·mysql·php·db
橘子134 小时前
MySQL用户管理(十三)
数据库·mysql
Dxy12393102164 小时前
MySQL如何加唯一索引
android·数据库·mysql
我真的是大笨蛋4 小时前
深度解析InnoDB如何保障Buffer与磁盘数据一致性
java·数据库·sql·mysql·性能优化
怣504 小时前
MySQL数据检索入门:从零开始学SELECT查询
数据库·mysql
人道领域5 小时前
javaWeb从入门到进阶(SpringBoot事务管理及AOP)
java·数据库·mysql
千寻技术帮6 小时前
10404_基于Web的校园网络安全防御系统
网络·mysql·安全·web安全·springboot
spencer_tseng7 小时前
MySQL table backup
mysql