從源碼到實戰(zhàn)盤點MySQL中不寫B(tài)inlog的N種場景
引言
Binlog(二進制日志)是MySQL的核心組件,負責(zé)記錄數(shù)據(jù)變更,支撐著主從復(fù)制、數(shù)據(jù)恢復(fù)等重要功能。但你是否遇到過這樣的疑問:
- 為什么
mysql.slow_log表的變更不會同步到從庫? SET sql_log_bin = 0到底做了什么?performance_schema的數(shù)據(jù)變化會記錄到binlog嗎?
本文將深入MySQL源碼,徹底解析哪些場景下MySQL不會寫入binlog,并通過源碼級別的分析,讓你不僅知其然,更知其所以然。
1. Binlog寫入的核心判斷邏輯
在MySQL源碼中,binlog寫入的入口函數(shù)是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)) {
// 根據(jù)操作類型調(diào)用不同的日志函數(shù)
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) {
// 緩存判斷結(jié)果,避免重復(fù)計算
if (table->s->cached_row_logging_check == -1) {
int const check(
table->s->tmp_table == NO_TMP_TABLE && // 不是臨時表
!table->no_replicate && // 允許復(fù)制
binlog_filter->db_ok(table->s->db.str) // 庫在復(fù)制白名單
);
table->s->cached_row_logging_check = check;
}
// 需要滿足4個條件才返回true
return (thd->is_current_stmt_binlog_format_row() && // 行格式
table->s->cached_row_logging_check && // 表允許復(fù)制
(thd->variables.option_bits & OPTION_BIN_LOG) && // 線程啟用binlog
mysql_bin_log.is_open()); // binlog已打開
}
結(jié)論:只要上述4個條件任意一個不滿足,操作就不會寫入binlog。
2. 場景一:臨時表操作
2.1 現(xiàn)象演示
-- 創(chuàng)建臨時表
CREATE TEMPORARY TABLE temp_user (
id INT PRIMARY KEY,
name VARCHAR(50)
);
-- 插入數(shù)據(jù)(不會寫入binlog)
INSERT INTO temp_user VALUES (1, '張三');
-- 更新數(shù)據(jù)(不會寫入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?

根本原因:臨時表的生命周期僅限于創(chuàng)建它的會話,對其他會話(包括從庫)不可見,因此無需同步。
3. 場景二:特殊系統(tǒng)表
3.1 不寫binlog的系統(tǒng)表列表
| 表類別 | 具體表名 | 作用 |
|---|---|---|
| 日志表 | mysql.general_log | 通用查詢?nèi)罩?/td> |
| 日志表 | mysql.slow_log | 慢查詢?nèi)罩?/td> |
| 復(fù)制信息表 | mysql.slave_relay_log_info | 從庫中繼日志信息 |
| 復(fù)制信息表 | mysql.slave_master_info | 從庫主庫連接信息 |
| 復(fù)制信息表 | mysql.slave_worker_info | 從庫并行復(fù)制信息 |
| GTID表 | mysql.gtid_executed | GTID執(zhí)行記錄 |
3.2 現(xiàn)象演示
-- 開啟慢日志記錄到表 SET GLOBAL log_output = 'TABLE'; SET GLOBAL slow_query_log = ON; -- 執(zhí)行慢查詢 SELECT SLEEP(2); -- 查看慢日志表(寫入成功) SELECT * FROM mysql.slow_log; -- 但這些記錄不會同步到從庫!
3.3 源碼分析
// sql/table.cc - open_table_from_share函數(shù)
if ((share->table_category == TABLE_CATEGORY_LOG) ||
(share->table_category == TABLE_CATEGORY_RPL_INFO) ||
(share->table_category == TABLE_CATEGORY_GTID)) {
outparam->no_replicate = true; // 這些表設(shè)置no_replicate標志
}
表類別定義:
// sql/table.h
enum enum_table_category {
TABLE_CATEGORY_USER, // 用戶表
TABLE_CATEGORY_LOG, // 日志表
TABLE_CATEGORY_RPL_INFO, // 復(fù)制信息表
TABLE_CATEGORY_GTID, // GTID表
TABLE_CATEGORY_INFORMATION_SCHEMA, // 信息模式
TABLE_CATEGORY_PERFORMANCE_SCHEMA // 性能模式
};
4. 場景三:Performance Schema表
4.1 現(xiàn)象演示
-- 嘗試修改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函數(shù)中
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. 場景四:顯式關(guān)閉binlog
5.1 會話級關(guān)閉
-- 關(guān)閉當前會話的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 源碼回調(diào)函數(shù)
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; // 關(guān)閉
return false;
}
5.3 典型應(yīng)用場景
-- 場景1:批量導(dǎo)入大量數(shù)據(jù),避免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:從庫配置不記錄重放日志 -- 在從庫上設(shè)置 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; // 關(guān)閉:從庫操作不記錄binlog
}

6. 場景六:臨時關(guān)閉binlog的代碼保護機制
6.1 Disable_binlog_guard類
MySQL在源碼中使用RAII機制臨時關(guān)閉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) {
// 構(gòu)造函數(shù)中關(guān)閉binlog
thd->variables.option_bits &= ~OPTION_BIN_LOG;
}
~Disable_binlog_guard() {
// 析構(gòu)函數(shù)中恢復(fù)
if (m_binlog_disabled)
m_thd->variables.option_bits |= OPTION_BIN_LOG;
}
private:
THD *const m_thd;
const bool m_binlog_disabled;
};
6.2 應(yīng)用場景
場景1:實例初始化(–initialize)
static bool handle_bootstrap_impl(handle_bootstrap_args *args) {
if (opt_initialize) {
const Disable_binlog_guard disable_binlog(thd);
// 初始化系統(tǒng)表,這些操作不寫入binlog
rc = process_iterator(thd, &comp_iter, true);
}
}
場景2:實例升級
bool upgrade_system_schemas(THD *thd) {
Disable_binlog_guard disable_binlog(thd);
// 升級系統(tǒng)表,不寫入binlog
err = fix_mysql_tables(thd) || fix_sys_schema(thd);
}
7. 場景七:NO_WRITE_TO_BINLOG關(guān)鍵字
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 源碼實現(xiàn)
// sql/sql_yacc.yy - 語法解析部分
opt_no_write_to_binlog:
/* Empty */ { $$ = false; }
| NO_WRITE_TO_BINLOG_SYM { $$ = true; }
| LOCAL_SYM { $$ = true; }
;
// 在語法規(guī)則中設(shè)置
repair_table_stmt:
REPAIR opt_no_write_to_binlog TABLE ...
{
Lex->sql_command = SQLCOM_REPAIR;
Lex->no_write_to_binlog = $2; // 設(shè)置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 現(xiàn)象
-- performance_schema表支持TRUNCATE TRUNCATE performance_schema.events_statements_summary_by_digest; -- 但不會寫入binlog -- 從庫不會執(zhí)行這個TRUNCATE操作
8.2 原因分析
// storage/perfschema/ha_perfschema.cc
int ha_perfschema::truncate() {
// 直接清空內(nèi)存數(shù)據(jù)結(jié)構(gòu)
reset_table_handles(m_table);
return 0;
// 不調(diào)用binlog_log_row,不寫入binlog
}
9. 總結(jié)與對比表格
9.1 所有場景匯總
| 場景 | 觸發(fā)條件 | 判斷依據(jù) | 典型示例 |
|---|---|---|---|
| 臨時表 | 操作臨時表 | tmp_table != NO_TMP_TABLE | CREATE TEMPORARY TABLE |
| 系統(tǒng)日志表 | 操作mysql.general_log等 | table_category == TABLE_CATEGORY_LOG | 慢日志寫入 |
| 復(fù)制信息表 | 操作mysql.slave_*表 | table_category == TABLE_CATEGORY_RPL_INFO | 復(fù)制狀態(tài)更新 |
| GTID表 | 操作mysql.gtid_executed | table_category == TABLE_CATEGORY_GTID | GTID記錄 |
| Performance Schema | 操作performance_schema表 | 存儲引擎標志HA_NO_BINLOG | TRUNCATE |
| 會話級關(guān)閉 | SET sql_log_bin = 0 | OPTION_BIN_LOG標志 | 批量導(dǎo)入 |
| 從庫不記錄 | log_replica_updates=OFF | opt_log_replica_updates | 從鏈式復(fù)制 |
| 內(nèi)部操作 | 實例初始化/升級 | Disable_binlog_guard | mysqld --initialize |
| 顯式指定 | NO_WRITE_TO_BINLOG | lex->no_write_to_binlog | OPTIMIZE LOCAL TABLE |
9.2 快速判斷指南

10. 最佳實踐建議
10.1 日常開發(fā)注意事項
-- 1. 不要期望慢日志表被復(fù)制 -- ? 錯誤想法:在從庫查詢慢日志 SELECT * FROM mysql.slow_log; -- 從庫可能是空的 -- ? 正確做法:在主庫查詢或通過監(jiān)控系統(tǒng)收集 -- 2. 批量操作記得關(guān)閉binlog -- 大批量數(shù)據(jù)導(dǎo)入 SET SESSION sql_log_bin = 0; -- 執(zhí)行批量操作 SET SESSION sql_log_bin = 1; -- 3. 維護操作使用LOCAL關(guān)鍵字 OPTIMIZE LOCAL TABLE large_table; -- 不寫binlog,減少網(wǎng)絡(luò)傳輸
10.2 復(fù)制環(huán)境配置建議
# 從庫配置:如果不是鏈式復(fù)制,建議關(guān)閉 [mysqld] log_replica_updates = OFF relay_log = relay-bin read_only = ON
結(jié)語
通過源碼級別的分析,我們可以看到MySQL對binlog寫入有著精細的控制:
- 內(nèi)部系統(tǒng)表:避免循環(huán)復(fù)制和性能開銷
- 臨時對象:遵循會話隔離原則
- 控制開關(guān):提供靈活的管理手段
- RAII保護:確保關(guān)鍵操作的安全性
理解這些場景,不僅能幫助你更好地理解MySQL的運行機制,還能在實際運維中做出更合理的選擇。
以上就是從源碼到實戰(zhàn)盤點MySQL中不寫B(tài)inlog的N種場景的詳細內(nèi)容,更多關(guān)于MySQL不寫B(tài)inlog的場景的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Hibernate4在MySQL5.1以上版本創(chuàng)建表出錯 type=InnDB
本文主要介紹解決Hibernate4在MySQL5.1自動創(chuàng)建表出錯的方法,簡單實用,需要的朋友可以參考下。2016-05-05
MySQL下將一個表的數(shù)據(jù)插入到另外一個表的實現(xiàn)語句
開發(fā)中,我們經(jīng)常需要將一個表的數(shù)據(jù)插入到另外一個表,有時還需要指定導(dǎo)入字段,雖然這個實現(xiàn)起來非常簡單,但是還是會困擾許多新手,因此專門發(fā)一篇文章備查。2009-09-09
mysql 使用存儲過程實現(xiàn)樹節(jié)點的獲取方法
這篇文章主要介紹了mysql 使用存儲過程實現(xiàn)樹節(jié)點的獲取方法,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-06-06
window下mysql 8.0.15 winx64安裝配置方法圖文教程
這篇文章主要為大家詳細介紹了window下mysql 8.0.15 winx64安裝配置方法圖文教程,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03
Windows下MySQL?8.0.29?安裝和刪除圖文教程
這篇文章主要為大家詳細介紹了Windows下MySQL?8.0.29?安裝和刪除圖文教程,文中安裝步驟介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-07-07

