丁奇:MySQL 中 6 個常見的日誌問題

2021-02-24 InfoQ
出處 | 極客時間《 MySQL 實戰 45 講》專欄

MySQL 裡有兩個日誌,即:重做日誌(redo log)和歸檔日誌(binlog)。

其中,binlog 可以給備庫使用,也可以保存起來用於恢復資料庫歷史數據。它是實現在 server 層的,所有引擎可以共用。redo log 是 InnoDB 特有的日誌,用來支持 crash-safe 能力。

你一定聽過 MySQL 事務的兩階段提交,指的就是在事務提交的時候,分成 prepare 和 commit 兩個階段。

如圖 1 所示為一個事務的執行流程,你在最後三步可以看到,redo log 先 prepare 完成,再寫 binlog,最後才進入 redo log commit 階段。

圖 1 兩階段提交示意圖

這裡,我要先和你解釋一個誤會式的問題:這個圖不就是一個 update 語句的執行流程嗎,怎麼還會調用 commit 語句?

通常情況下,你會產生這個疑問的原因,在於把兩個「commit」的概念混淆了:

問題中的「commit 語句」,是指 MySQL 語法中,用於提交一個事務的命令。一般跟 begin/start transaction 配對使用。

而我們圖中用到的這個「commit 步驟」,指的是事務提交過程中的一個小步驟,也是最後一步。當這個步驟執行完成後,這個事務就提交完成了。

「commit 語句」執行的時候,會包含「commit 步驟」。

而我們這個例子裡面,沒有顯式地開啟事務,因此這個 update 語句自己就是一個事務,在執行完成後提交事務時,就會用到這個「commit 步驟」。

丁奇《MySQL 實戰 45 講》,「碼」上訂閱

接下來,我們就一起分析一下在兩階段提交的不同時刻,MySQL 異常重啟會出現什麼現象。

如果在圖中時刻 A 的地方,也就是寫入 redo log 處於 prepare 階段之後、寫 binlog 之前,發生了崩潰(crash),由於此時 binlog 還沒寫,redo log 也還沒提交,所以崩潰恢復的時候,這個事務會回滾。這時候,binlog 還沒寫,所以也不會傳到備庫。到這裡,我們都可以理解。

而我們理解會出現問題的地方,主要集中在時刻 B,也就是 binlog 寫完,redo log 還沒 commit 前發生 crash,那崩潰恢復的時候 MySQL 會怎麼處理?

我們先來看一下崩潰恢復時的判斷規則。

1、如果 redo log 裡面的事務是完整的,也就是已經有了 commit 標識,則直接提交;

2、如果 redo log 裡面的事務只有完整的 prepare,則判斷對應的事務 binlog 是否存在並完整:

      a.如果是,則提交事務;

      b.否則,回滾事務。

這裡,時刻 B 發生 crash 對應的就是 2(a) 的情況,崩潰恢復過程中事務會被提交。

現在,我們就針對兩階段提交再繼續延展一下。

問題 1:MySQL 怎麼知道 binlog 是完整的?

回答:一個事務的 binlog 是有完整格式的:

另外,在 MySQL 5.6.2 版本以後,還引入了 binlog-checksum 參數,用來驗證 binlog 內容的正確性。對於 binlog 日誌由於磁碟原因,可能會在日誌中間出錯的情況,MySQL 可以通過校驗 checksum 的結果來發現。所以,MySQL 還是有辦法驗證事務 binlog 的完整性的。

問題 2:redo log 和 binlog 是怎麼關聯起來的?

回答:它們有一個共同的數據欄位,叫 XID。崩潰恢復的時候,會按順序掃描 redo log:

問題 3:處於 prepare 階段的 redo log 加上完整 binlog,重啟就能恢復,MySQL 為什麼要這麼設計?

回答:其實,這個問題還是跟我們在反證法中說到的數據與備份的一致性有關。在時刻 B,也就是 binlog 寫完以後 MySQL 發生崩潰,這時候 binlog 已經寫入了,之後就會被從庫(或者用這個 binlog 恢復出來的庫)使用。

所以,在主庫上也要提交這個事務。採用這個策略,主庫和備庫的數據就保證了一致性。

問題 4:如果這樣的話,為什麼還要兩階段提交呢?乾脆先 redo log 寫完,再寫 binlog。崩潰恢復的時候,必須得兩個日誌都完整才可以。是不是一樣的邏輯?

回答:其實,兩階段提交是經典的分布式系統問題,並不是 MySQL 獨有的。

如果必須要舉一個場景,來說明這麼做的必要性的話,那就是事務的持久性問題。

對於 InnoDB 引擎來說,如果 redo log 提交完成了,事務就不能回滾(如果這還允許回滾,就可能覆蓋掉別的事務的更新)。而如果 redo log 直接提交,然後 binlog 寫入的時候失敗,InnoDB 又回滾不了,數據和 binlog 日誌又不一致了。

兩階段提交就是為了給所有人一個機會,當每個人都說「我 ok」的時候,再一起提交。

問題 5:不引入兩個日誌,也就沒有兩階段提交的必要了。只用 binlog 來支持崩潰恢復,又能支持歸檔,不就可以了?

回答:我把這個問題再翻譯一下的話,是說只保留 binlog,然後可以把提交流程改成這樣:... -> 「數據更新到內存」 -> 「寫 binlog」 -> 「提交事務」,是不是也可以提供崩潰恢復的能力?

答案是不可以。

如果說歷史原因的話,那就是 InnoDB 並不是 MySQL 的原生存儲引擎。MySQL 的原生引擎是 MyISAM,設計之初就有沒有支持崩潰恢復。

InnoDB 在作為 MySQL 的插件加入 MySQL 引擎家族之前,就已經是一個提供了崩潰恢復和事務支持的引擎了。

InnoDB 接入了 MySQL 後,發現既然 binlog 沒有崩潰恢復的能力,那就用 InnoDB 原有的 redo log 好了。

而如果說實現上的原因的話,就有很多了。就按照問題中說的,只用 binlog 來實現崩潰恢復的流程,我畫了一張示意圖,這裡就沒有 redo log 了。

圖 2 只用 binlog 支持崩潰恢復

這樣的流程下,binlog 還是不能支持崩潰恢復的。我說一個不支持的點吧:binlog 沒有能力恢復「數據頁」。

如果在圖中標的位置,也就是 binlog2 寫完了,但是整個事務還沒有 commit 的時候,MySQL 發生了 crash。

重啟後,引擎內部事務 2 會回滾,然後應用 binlog2 可以補回來;但是對於事務 1 來說,系統已經認為提交完成了,不會再應用一次 binlog1。

但是,InnoDB 引擎使用的是 WAL 技術,執行事務的時候,寫完內存和日誌,事務就算完成了。如果之後崩潰,要依賴於日誌來恢復數據頁。

也就是說在圖中這個位置發生崩潰的話,事務 1 也是可能丟失了的,而且是數據頁級的丟失。此時,binlog 裡面並沒有記錄數據頁的更新細節,是補不回來的。

你如果要說,那我優化一下 binlog 的內容,讓它來記錄數據頁的更改可以嗎?可以,但這其實就是又做了一個 redo log 出來。

所以,至少現在的 binlog 能力,還不能支持崩潰恢復。

問題 6:那能不能反過來,只用 redo log,不要 binlog?

回答:如果只從崩潰恢復的角度來講是可以的。你可以把 binlog 關掉,這樣就沒有兩階段提交了,但系統依然是 crash-safe 的。

但是,如果你了解一下業界各個公司的使用場景的話,就會發現在正式的生產庫上,binlog 都是開著的。因為 binlog 有著 redo log 無法替代的功能。

一個是歸檔。redo log 是循環寫,寫到末尾是要回到開頭繼續寫的。這樣歷史日誌沒法保留,redo log 也就起不到歸檔的作用。

一個就是 MySQL 系統依賴於 binlog。binlog 作為 MySQL 一開始就有的功能,被用在了很多地方。其中,MySQL 系統高可用的基礎,就是 binlog 複製。

還有很多公司有異構系統(比如一些數據分析系統),這些系統就靠消費 MySQL 的 binlog 來更新自己的數據。關掉 binlog 的話,這些下遊系統就沒法輸入了。

總之,由於現在包括 MySQL 高可用在內的很多系統機制都依賴於 binlog,所以「鳩佔鵲巢」 redo log 還做不到。你看,發展生態是多麼重要。

最後,推薦你關注丁奇的《MySQL 實戰 45 講》專欄。在專欄裡,丁奇會幫你梳理出學習 MySQL 的主線知識,比如事務、索引、鎖等,還會就開發過程中經常遇到的具體問題和你分析討論,並且幫你理解問題背後的本質。你會收穫 MySQL 核心技術詳解與原理說明和 36 個 MySQL 常見痛點問題解析。

現在訂閱,限時優惠¥79,原價¥99,1 月 14 日恢復原價。

相關焦點

  • MySQL日誌安全分析技巧
    常見的資料庫攻擊包括弱口令、SQL注入、提升權限、竊取備份等。
  • MySQL 資料庫崩潰(crash)的常見原因和解決辦法
    檢查 MySQL 資料庫的啟動時間Linux 系統中的 systemd 會在 mysqld 進程 crash 後自動重新啟動 MySQL 的服務,需要注意的是使用 kill -9 殺死 mysqld 進程系統會自動重新啟動,而只使用 kill 命令則不會重新啟動,因為執行 kill 命令,系統會發送一個 SIGTERM 信號給 mysqld,mysql 資料庫會正常關閉,日誌中會出現類似下面的記錄
  • MySQL-5.7.28安裝中的部分問題及解決方案
    解決方案:此電腦->右鍵->管理->服務和應用程式->服務->找到MySQL,右鍵點擊運行 如果依然不可行,在cmd輸入  mysql --console   查詢問題日誌
  • Centos7.6安裝mysql
    在CentOS中默認安裝有MariaDB,而我們需要用的mysql,所有需要將其覆蓋掉,下面是安裝過程:1、下載並安裝MySQL
  • MysqlDump備份數據
    備份單個資料庫的結構mysqldump -uroot -p123456 mydb -d > /data/mysqlDump/mydb.sql6. 備份單個資料庫的數據mysqldump -uroot -p123456 mydb -t > /data/mysqlDump/mydb.sql7.
  • mysqldump使用筆記
    ②--dump-slave,該參數可以用作在從庫做備份獲取主庫的位置點,來做一個新從庫,避免在主庫做備份影響業務,帶該參數備份時,從上sql線程會被kill,備份完再拉起常見用法:mysqldump --single-transaction -B test a > backup.sql 備份test庫和a庫mysqldump -
  • Day1 | 認識MySQL
    資料庫分類當今網際網路中,最常見的資料庫模型主要為兩種,即關係型資料庫(SQL)和非關係型資料庫(NoSQL 、Not Only SQL)。關係型資料庫非關係型資料庫1、定義:數據存儲不需要固定的表結構,通常也不存在連接操作。
  • 資料庫系列之MySQL主從複製集群部署
    返回信息中除了日誌所包含的信息之外,還包括本次返回的信息的bin-log file 以及bin-log position。從庫啟動之後,會創建一個I/O線程,用來讀取主庫傳過來的binlog內容並寫入到relay log中從庫創建一個SQL線程,從relay log裡面讀取內容解析成在主庫上實際執行過的操作,並在從庫的本地database中執行2.1.2 主從複製中的三個進程主從複製的完成通過以下三個進程實現的:
  • Percona MySQL Server 部署指南
    )# 此錯誤計數僅在 mysql 連結握手失敗才會計算,一般出現問題時都是網絡故障# refs https://www.cnblogs.com/kerrycode/p/8405862.htmlmax_connect_errors=100000# mysql server 允許的最大數據包大小max_allowed_packet=64M# 交互式客戶端連結超時
  • MySQL和PostgreSQL資料庫安全配置
    (一)、MySQL資料庫          MySQL資料庫在安裝後默認存在mysql資料庫,該資料庫為系統表所在的資料庫,所有用戶記錄在mysql資料庫的user三個權限表的用戶列(包括host、user、password三個欄位)。
  • 第一次玩docker的體驗:從0搭建docker應用tomcat,mysql,redis
    也可以使用systemctl start docker.servicesystemctl stop docker.servicesystemctl restart docker.service安裝常見的
  • MySQL 定時備份資料庫詳解(非常全)!
    備份單個資料庫的結構mysqldump -uroot -p123456 mydb -d > /data/mysqlDump/mydb.sql6. 備份單個資料庫的數據mysqldump -uroot -p123456 mydb -t > /data/mysqlDump/mydb.sql7.
  • MySQL、Redis、MongoDB 網絡抓包工具
    類似於在之前的文章 MySQL抓包工具:MySQL Sniffer中介紹的mysql-sniffer。而 go-sniffer 可以對更多資料庫進行抓包分析,現在來介紹在什麼情況下會使用該工具的。如對一個實例進行監控:go-sniffer eth0 redis -p 6379 >> out.log 對通過eth0網卡的客戶端訪問埠為6379的Redis服務進行抓包,並把信息寫到文件中。
  • MySQL 資料庫誤刪除後的數據恢復操作說明
    二、數據恢復思路(1)利用全備的sql文件中記錄的CHANGE MASTER語句,binlog文件及其位置點信息,找出binlog文件中增量的那部分。(2)用mysqlbinlog命令將上述的binlog文件導出為sql文件,並剔除其中的drop語句。
  • MySQL定時備份資料庫方案(全庫備份)
    -p123456 < /data/mysqlDump/mydb.sql2.在登錄進入mysql系統中,通過source指令找到對應系統中的文件進行還原:mysql> source /data/mysqlDump/mydb.sql二、 編寫腳本維護備份的資料庫文件
  • MySQL備份和恢復方法匯總
    學吧,學無止境,太深了!
  • Linux軟體安裝--二進位發布包安裝、rpm發布包安裝(案例:jdk和mysql安裝)
    當然如mysql/jdk也可以採用這種方式安裝,但安裝軟體可能需要依賴於其他軟體,這個方法不能自動解決包依賴的問題。示例:rpm發布包安裝mysql。可以先安裝一下依賴:yum install libaio安裝日誌看出安裝位置:/usr/sbin/mysqld (mysqld 5.6.26) starting as process 3360 ...安裝日誌看出默認初始密碼位置
  • MySQL 定時備份的幾種方式,寫得真Nice !
    ; /data/mysqlDump/mydb.sql1.3、 還原mysql備份內容有兩種方式還原,第一種是在MySQL命令行中,第二種是使用SHELL行完成還原1.在系統命令行中,輸入如下實現還原:mysql -uroot -p123456 < /data/mysqlDump/mydb.sql
  • MySQL 入門常用命令大全
    ,delete 可以使用 where 子句有選擇地進行刪除;(3)delete 每次刪除一行,並在事務日誌中為所刪除的每行記錄一項。truncate 釋放存儲表數據所用的數據頁來刪除數據,並且只在事務日誌中記錄頁的釋放,所以truncate 比 delete 使用的系統和事務日誌資源更少,效率更高;(4)truncate 導致自動增加欄位的初始值被重置,delete 沒有影響,自增欄位的值還是按照最後一次插入的基礎上遞增;(5)對於由 FOREIGN KEY 約束引用的表,不能使用 truncate,而應使用不帶
  • 小白都能懂的Mysql主從複製原理(原理+實操)
    Mysql的主從複製中主要有三個線程:master(binlog dump thread)、slave(I/O thread 、SQL thread),Master一條線程和Slave中的兩條線程。然後,將binlog保存在 「relay log(中繼日誌)」 中,中繼日誌也是記錄數據更新的信息。SQL線程也是在Slave中創建的,當Slave檢測到中繼日誌有更新,就會將更新的內容同步到Slave資料庫中,這樣就保證了主從的數據的同步。