MySQL是怎么保證數(shù)據(jù)不丟的

如何保證 redo log 真實地 寫入了磁盤

binlog 的寫入邏輯比較簡單:事務(wù)執(zhí)行過程中,先把日志寫到 binlog cache,事 務(wù)提交的時候,再把 binlog cache 寫到 binlog 文件中。
一個事務(wù)的 binlog 是不能被拆開的,因此不論這個事務(wù)多大,也要確保一次性寫入。這 就涉及到了 binlog cache 的保存問題。
系統(tǒng)給 binlog cache 分配了一片內(nèi)存,每個線程一個,參數(shù) binlog_cache_size 用于控 制單個線程內(nèi) binlog cache 所占內(nèi)存的大小。如果超過了這個參數(shù)規(guī)定的大小,就要暫 存到磁盤。
事務(wù)提交的時候,執(zhí)行器把 binlog cache 里的完整事務(wù)寫入到 binlog 中,并清空 binlog cache


image.png

write 和 fsync 的時機,是由參數(shù) sync_binlog 控制的:

  1. sync_binlog=0 的時候,表示每次提交事務(wù)都只 write,不 fsync;
  2. sync_binlog=1 的時候,表示每次提交事務(wù)都會執(zhí)行 fsync;
  3. sync_binlog=N(N>1) 的時候,表示每次提交事務(wù)都 write,但累積 N 個事務(wù)后才
    fsync。

InnoDB 有一個后臺線程,每隔 1 秒,就會把 redo log buffer 中的日志,調(diào)用 write 寫 到文件系統(tǒng)的 page cache,然后調(diào)用 fsync 持久化到磁盤。

注意,事務(wù)執(zhí)行中間過程的 redo log 也是直接寫在 redo log buffer 中的,這些 redo log 也會被后臺線程一起持久化到磁盤。也就是說,一個沒有提交的事務(wù)的 redo log,也 是可能已經(jīng)持久化到磁盤的。

還有兩種場景會讓一個沒有提交的事務(wù)的 redo log 寫入到磁盤中。

  1. 一種是,redo log buffer 占用的空間即將達到 innodb_log_buffer_size 一半的時 候,后臺線程會主動寫盤。注意,由于這個事務(wù)并沒有提交,所以這個寫盤動作只是 write,而沒有調(diào)用 fsync,也就是只留在了文件系統(tǒng)的 page cache。
  2. 另一種是,并行的事務(wù)提交的時候,順帶將這個事務(wù)的 redo log buffer 持久化到磁 盤。假設(shè)一個事務(wù) A 執(zhí)行到一半,已經(jīng)寫了一些 redo log 到 buffer 中,這時候有另 外一個線程的事務(wù) B 提交,如果 innodb_flush_log_at_trx_commit 設(shè)置的是 1,那么 按照這個參數(shù)的邏輯,事務(wù) B 要把 redo log buffer 里的日志全部持久化到磁盤。這時 候,就會帶上事務(wù) A 在 redo log buffer 里的日志一起持久化到磁盤。

通常我們說 MySQL 的“雙 1”配置,指的就是 sync_binlog 和 innodb_flush_log_at_trx_commit 都設(shè)置成 1。也就是說,一個事務(wù)完整提交前,需要 等待兩次刷盤,一次是 redo log(prepare 階段),一次是 binlog。

寫 binlog 是分成兩步的:

  1. 先把 binlog 從 binlog cache 中寫到磁盤上的 binlog 文件;
  2. 調(diào)用 fsync 持久化。
  3. redo log 和 binlog 都是順序?qū)?,磁盤的順序?qū)懕入S機寫速度要快; 2. 組提交機制,可以大幅度降低磁盤的 IOPS 消耗。
如果你的 MySQL 現(xiàn)在出現(xiàn)了性能瓶頸,而且瓶頸 在 IO 上,可以通過哪些方法來提升性能呢
  1. 設(shè)置 binlog_group_commit_sync_delay 和 binlog_group_commit_sync_no_delay_count 參數(shù),減少 binlog 的寫盤次數(shù)。這個 方法是基于“額外的故意等待”來實現(xiàn)的,因此可能會增加語句的響應(yīng)時間,但沒有丟 失數(shù)據(jù)的風(fēng)險。
  2. 將 sync_binlog 設(shè)置為大于 1 的值(比較常見是 100~1000)。這樣做的風(fēng)險是,主 機掉電時會丟 binlog 日志。
  3. 將 innodb_flush_log_at_trx_commit 設(shè)置為 2。這樣做的風(fēng)險是,主機掉電的時候會 丟數(shù)據(jù)。

不建議你把 innodb_flush_log_at_trx_commit 設(shè)置成 0。因為把這個參數(shù)設(shè)置成 0, 表示 redo log 只保存在內(nèi)存中,這樣的話 MySQL 本身異常重啟也會丟數(shù)據(jù),風(fēng)險太 大。而 redo log 寫到文件系統(tǒng)的 page cache 的速度也是很快的,所以將這個參數(shù)設(shè)置 成 2 跟設(shè)置成 0 其實性能差不多,但這樣做 MySQL 異常重啟時就不會丟數(shù)據(jù)了,相比之 下風(fēng)險會更小。

問題 1:執(zhí)行一個 update 語句以后,我再去執(zhí)行 hexdump 命令直接查看 ibd 文件內(nèi) 容,為什么沒有看到數(shù)據(jù)有改變呢?
回答:這可能是因為 WAL 機制的原因。update 語句執(zhí)行完成后,InnoDB 只保證寫完了 redo log、內(nèi)存,可能還沒來得及將數(shù)據(jù)寫到磁盤。
問題 2:為什么 binlog cache 是每個線程自己維護的,而 redo log buffer 是全局共用 的?
回答:MySQL 這么設(shè)計的主要原因是,binlog 是不能“被打斷的”。一個事務(wù)的 binlog 必須連續(xù)寫,因此要整個事務(wù)完成后,再一起寫到文件里。
而 redo log 并沒有這個要求,中間有生成的日志可以寫到 redo log buffer 中。redo log buffer 中的內(nèi)容還能“搭便車”,其他事務(wù)提交的時候可以被一起寫到磁盤中。

問題 3:事務(wù)執(zhí)行期間,還沒到提交階段,如果發(fā)生 crash 的話,redo log 肯定丟了,這 會不會導(dǎo)致主備不一致呢?
回答:不會。因為這時候 binlog 也還在 binlog cache 里,沒發(fā)給備庫。crash 以后 redo log 和 binlog 都沒有了,從業(yè)務(wù)角度看這個事務(wù)也沒有提交,所以數(shù)據(jù)是一致的。

問題 4:如果 binlog 寫完盤以后發(fā)生 crash,這時候還沒給客戶端答復(fù)就重啟了。等客戶 端再重連進來,發(fā)現(xiàn)事務(wù)已經(jīng)提交成功了,這是不是 bug?
回答:不是。
你可以設(shè)想一下更極端的情況,整個事務(wù)都提交成功了,redo log commit 完成了,備庫 也收到 binlog 并執(zhí)行了。但是主庫和客戶端網(wǎng)絡(luò)斷開了,導(dǎo)致事務(wù)成功的包返回不回 去,這時候客戶端也會收到“網(wǎng)絡(luò)斷開”的異常。這種也只能算是事務(wù)成功的,不能認(rèn)為 是 bug。
實際上數(shù)據(jù)庫的 crash-safe 保證的是:

  1. 如果客戶端收到事務(wù)成功的消息,事務(wù)就一定持久化了;
  2. 如果客戶端收到事務(wù)失敗(比如主鍵沖突、回滾等)的消息,事務(wù)就一定失敗了;
  3. 如果客戶端收到“執(zhí)行異?!钡南ⅲ瑧?yīng)用需要重連后通過查詢當(dāng)前狀態(tài)來繼續(xù)后續(xù)的
    邏輯。此時數(shù)據(jù)庫只需要保證內(nèi)部(數(shù)據(jù)和日志之間,主庫和備庫之間)一致就可以了。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容