制和數(shù)據(jù)恢復(fù)的基礎(chǔ))
binlog 是什么binlog 是 Server 層的歸檔日志記的是數(shù)據(jù)發(fā)生了哪些變更。和 redo log 擺在一起看差別最明顯的是三點redo log 是循環(huán)寫的空間固定寫滿就覆蓋binlog 是追加寫的寫完一個文件換下一個歷史都在redo log 屬于 InnoDBMyISAM 根本沒有binlog 屬于 Server 層用什么引擎都有redo log 是物理邏輯日志綁死在頁和偏移上binlog 是邏輯日志記的是語句或者行前兩點決定了 binlog 能長期保留、能跨引擎第三點決定了它能跨版本、跨機器用。主從復(fù)制和按時間點恢復(fù)這兩件事只有 binlog 干得了。三種格式binlog_format有三個取值這是 binlog 最需要搞清楚的地方。STATEMENT記原始的 SQL 語句。UPDATE t SET name b WHERE id 1記的就是這條 SQL 本身。好處是日志小。一條UPDATE改了 100 萬行也只需要記一條語句帶來的幾十個字節(jié)。坑在于不確定的語句會讓主從不一致UPDATEtSETcreate_timeNOW()WHEREid1;UPDATEtSETtokenUUID()WHEREid1;UPDATEtSETnn1WHEREid1LIMIT1;主庫執(zhí)行的時候NOW()是 10:00:00binlog 里記的是NOW()這個函數(shù)調(diào)用從庫回放的時候是 10:00:05寫進去的時間就不一樣了。UUID()、RAND()同理LIMIT不帶ORDER BY的時候取哪一行也不確定。這類語句在主從環(huán)境里是隱藏的雷等到發(fā)現(xiàn)數(shù)據(jù)對不上往往已經(jīng)過了很久。ROW不記 SQL記每一行改前和改后的值。UPDATE t SET name b WHERE id 1在 ROW 格式下會變成兩個事件Table_map_event 記錄涉及的表結(jié)構(gòu)表 id、列數(shù)、每列的類型 Update_rows_event 記錄 id 1 這一行name 從 a 變成 bTable_map_event是給從庫看的因為從庫要按列的位置來解析行數(shù)據(jù)得先知道表長什么樣。好處是結(jié)果確定。不管 SQL 里寫了NOW()還是UUID()binlog 里存的是最終寫進去的那個值從庫照著寫就行不可能不一致。代價是日志大。一條UPDATE改 100 萬行就是 100 萬行的前后值日志體積可能比 STATEMENT 大幾十倍。所以 ROW 格式下max_binlog_size會經(jīng)常觸發(fā)文件切換磁盤占用也漲得快。MySQL 5.7.7 之后默認(rèn)就是 ROW 格式這也是現(xiàn)在的主流選擇。ROW 格式還有個參數(shù)binlog_row_image控制記多少列full默認(rèn)記所有列的前后值minimal只記被改的列加上主鍵noblob是full的變體BLOB 和 TEXT 列只在真的被改時才記。寫多讀少的場景調(diào)成minimal能省不少空間。MIXEDMySQL 自己判斷默認(rèn)用 STATEMENT碰到不確定的函數(shù)就切到 ROW。聽起來兩全其美實際用的人不多。因為哪些語句算不確定由 MySQL 判斷判斷的邊界不透明出了問題不好排查不如直接上 ROW 求個確定。怎么選格式日志體積主從一致性說明STATEMENT小有隱患除非有明確的體積壓力不建議ROW大確定現(xiàn)在的主流5.7.7 之后是默認(rèn)值MIXED中依賴判斷判斷邊界不透明用得少一句話用磁盤空間換確定性選 ROW。寫盤的時機binlog 的寫入路徑比 redo log 簡單一層事務(wù)執(zhí)行 → binlog cache線程私有→ binlog 文件 → 磁盤注意binlog cache是每個線程一份的不像 redo log buffer 是全局共享的。原因是一個事務(wù)的 binlog 必須連續(xù)完整地放在一起如果所有線程共用一個緩沖區(qū)兩個事務(wù)的 binlog 會交織在一起從庫沒法按事務(wù)回放。binlog_cache_size控制這個緩沖區(qū)大小不夠用的時候會溢寫到磁盤上的臨時文件事務(wù)結(jié)束后刪掉。提交的時候分兩步把binlog cache里的內(nèi)容寫進 binlog 文件這一步是write還沒落盤按sync_binlog決定要不要fsync0不主動 fsync交給操作系統(tǒng)決定機器掉電會丟1每次提交都 fsync默認(rèn)值N累積 N 個事務(wù)后 fsync 一次sync_binlog 0和innodb_flush_log_at_trx_commit 2有點像都是數(shù)據(jù)交給 OS 了但沒落盤進程崩潰不丟、機器掉電會丟。線上最嚴(yán)的配置是sync_binlog 1加上innodb_flush_log_at_trx_commit 1俗稱雙 1含義是提交返回了就一定不會丟。還有一條規(guī)則一個事務(wù)的 binlog 不能被拆到兩個文件里。binlog 文件按max_binlog_size默認(rèn) 1GB切換但如果切換的時候有事務(wù)正在寫會等這個事務(wù)寫完再切。所以實際的文件大小可能略超過 1GB。主從復(fù)制靠它復(fù)制的整個機制就是把主庫的 binlog 搬到從庫執(zhí)行一遍中間靠三個線程接力主庫 從庫 ┌─────────────┐ ┌──────────────────┐ │ binlog │ │ relay log │ └─────────────┘ └──────────────────┘ ↑ ↑ ↓ dump 線程 ──── 推 binlog ────→ IO 線程 SQL 線程 ↓ 執(zhí)行到從庫從庫的 IO 線程連上主庫告訴主庫我要從某個 binlog 文件的某個位置開始主庫的 dump 線程從那個位置開始讀 binlog推給從庫從庫的 IO 線程收到后寫進本地的 relay log從庫的 SQL 線程讀 relay log在從庫上把事件重放一遍為什么中間要落一份 relay log不直接從 IO 線程交給 SQL 線程為了讓兩個線程解耦。SQL 線程回放慢的時候不會卡住 IO 線程繼續(xù)拉取主庫掛了從庫手里也有存貨能繼續(xù)回放。另外 SQL 線程可以并行IO 線程只能串行拉分開才好做并行復(fù)制。數(shù)據(jù)恢復(fù)靠它有了全量備份加上 binlog就能恢復(fù)到任意一個時間點這個叫PITR( Point-In-Time Recovery )。流程是兩步先把最近一次全量備份恢復(fù)回去再把備份之后到目標(biāo)時間點之間的 binlog 重放一遍。mysqlbinlog\--start-datetime2026-09-23 10:00:00\--stop-datetime2026-09-23 10:30:00\--databasetest\mysql-bin.000001|mysql-uroot-p舉例凌晨 2 點做了全量備份上午 10 點有人誤刪了一張表想恢復(fù)到 10 點之前。那就恢復(fù)備份然后把凌晨 2 點到 9 點 59 分之間的 binlog 重放一遍。這也是為什么誤刪數(shù)據(jù)之后第一件事不是著急操作而是先把 binlog 保護起來因為它是唯一能找回數(shù)據(jù)的憑據(jù)。--start-datetime和--stop-datetime可以換成--start-position和--stop-position用精確的位點比時間更準(zhǔn)。時間點恢復(fù)的時候要找誤操作之前的那個位點通常用--stop-position卡住。ROW 格式的 binlog 直接看是二進制要用mysqlbinlog -vv解碼。-v會把行變更轉(zhuǎn)成偽 SQL-vv還會帶上列的類型注釋排查問題的時候基本都用-vv。為什么有了 redo log 還要 binlog這個問題在 Redo LogMySQL 為什么能夠?qū)崿F(xiàn)崩潰恢復(fù)那篇里提過一半這里補全。兩個日志誰也替代不了誰有四個原因redo log 是循環(huán)寫的空間固定寫滿就覆蓋。它只夠支撐崩潰之后把數(shù)據(jù)補回來撐不起保留過去一個月所有變更redo log 屬于 InnoDBMyISAM 之類的引擎沒有。復(fù)制和恢復(fù)是 Server 層的需求不能綁在某一個引擎上redo log 是物理邏輯日志綁死頁結(jié)構(gòu)和偏移。換個 MySQL 大版本、換個平臺頁的格式可能就變了日志沒法通用從庫和主庫不一定完全一樣頁大小、數(shù)據(jù)文件布局都可能不同物理日志搬過去對不上binlog 是邏輯的、歸檔的、Server 層的這三條才是復(fù)制和恢復(fù)需要的性質(zhì)。