筆者在閱讀《高性能MySQL》的過程中,發現本書對事務日志的介紹過於晦澀也過於簡略,因此結合自己的理解,詳細地寫一下事務日志。
InnoDB的事務日志主要分為redo log(重做日志,提供前滾操作)和undo log(回滾日志,提供回滾操作),為了最大程度上減少數據寫入時io問題,在存儲引擎修改表的數據時,會將數據從磁盤拷貝到內存中,然后修改內存中的數據拷貝,再將修改行為持久化到磁盤中(先寫redo log buffer(日志緩沖區)(PS:這塊我會在下文詳細說明),再定期批量寫入),而不用每次將修改的數據本身持久化到硬盤中。
(PS:這里筆者有個疑問,redo log本身是循環寫,但事務日志是追加寫,對此我猜測是因為redo log相對於需要寫入的數據來說足夠大,所以對單個事務的寫入來說是追加寫操作,但是從redo log整體來說,其空間是循環利用的,當空間占用到一定程度后,就會從頭開始循環寫數據。因此此處循環寫和追加寫的概念不沖突,這是筆者的猜想,水平有限可能並不准確,如果以后有了答案會來更新,當然如果有人知道也請不吝賜教)。
當事務日志持久化以后,就會將臟數據慢慢寫回磁盤中(這塊下面我也會詳細說),目前大多數存儲引擎都是這么做的,這也被叫做預寫式日志(Write-Ahead Logging),修改數據的時候需要進行兩次寫磁盤的操作。
其實上面的概念都是老生常談,下面我會詳細地講講事務日志的各個部分。
redo log_buffer
事務日志本身也有緩存,這就是redo log_buffer(日志緩沖區),每次事務日志的寫入並不會直接寫入到文件中,而是會寫入到緩沖區中,在一定事件的觸發下,才會將緩沖區內的數據寫入到日志文件中。
也就是說,一個寫操作在InnoDB引擎內部發生的事情其實是這樣的:Write info -->redo log info-->redo log buffer -->redo log file -->file。
innoDB_log_buffer_size的大小:
mysql> show variables like 'innodb_log_buffer_size%'; +------------------------+----------+ | Variable_name | Value | +------------------------+----------+ | innodb_log_buffer_size | 16777216 | +------------------------+----------+ 1 row in set (0.05 sec)
我電腦中的日志緩沖區大小是16777216,也就是16M。redo log buffer的空間不需要太大,因為每秒都會刷新緩存到日志文件,因此緩存空間只需要大過每秒產生的事務量就可以了。
之前說在一定事件的觸發下,才會將緩沖區內的數據寫到日志文件中(LGWR),那么到底是哪些時間呢?其一,就是剛剛提到的,每一秒鍾主線程都會自動刷新;第二,當事務提交時,也會將緩沖區中的數據刷新到文件中;最后,只要緩沖區中的剩余空間小於總空間的一半,就會立刻刷新數據。
redo log
redo log(重做日志)本身是數據庫事務中至關重要的部分。因為如果沒有redo log的話,每修改一次數據,就需要移動一次磁頭,而且一旦在修改過程中服務器斷電的話,就會造成無法想象的后果。因此,我們引入了redo log,每次修改數據的時候,只需要順序移動磁頭即可,效率很高,一旦出現崩潰的情況,未寫入數據庫的數據也不會影響事務的完整性。在引擎重啟后,會自動恢復這部分修改的數據,將數據庫恢復到之前的正常狀態(也就是說不需要重做所有日志,只需要重做Check Point之后的日志就可以了),這就是crash safe。
之前也說了redo log是循環寫入數據的,每個InnoDB引擎至少有一個重做日志文件組,而每個重做日志文件組至少包含兩個重做日志文件(ib_logfile0,ib_logfile1),當文件0寫滿后,就切換到文件1繼續寫;文件1寫滿了,就切換回文件0。
在數據寫入量比較大的時候,redo log的大小會影響到數據庫的性能,太大的話,一旦需要恢復日志,就需要很長時間。太小的話,寫入一個事務的時候可能需要多次切換重做日志文件,會導致性能抖動。
mysql> show variables like 'innodb_log_file_size'; +----------------------+----------+ | Variable_name | Value | +----------------------+----------+ | innodb_log_file_size | 50331648 | +----------------------+----------+ 1 row in set (0.00 sec)
目前5.7.24-ndb-7.6.8-cluster-gpl 版本中的默認空間是48M,最大可以設置為4G。
undo log
undo log的作用是用來提供事務的回滾功能,以及多個行版本控制(MVCC),即非鎖定讀。undo log並不是redo log的逆向過程,事實上,二者都算是用來提供數據恢復的日志。不同的是,redo log是物理日志,而undo log是邏輯日志,比如我們執行了一條update更新操作,undo log就會將update的反向操作記錄下來,而redo log則只會記錄修改后的數據值。並且undo log本身也需要持久化支持,所以undo log也會產生redo log。
不同於redo log是在一片連續的磁盤空間上,undo log則是在幾片獨立的磁盤空間中,筆者猜想這是為了效率考慮,可以同時處理多個事務的數據修改。
總結一下,undo log是用來在事務失敗后進行回滾(roll back),保證了事務的原子性。
redo是用來恢復未寫入data file里的已成功事務更新的數據,保證了事務的持久性。
二者缺一不可,如果只有redo,就不能在事務提交前刷新臟數據,這樣一旦處理大事務,內存的占用會高的讓你無法想象;而只有undo的話,就必須要在事務提交前刷臟完成,否則一旦宕機,某些數據就會直接消失,破壞了持久性。
這里解釋下什么是臟數據,因為進行的修改並不會立刻持久化到硬盤空間中,就會造成有一段時間data file和redo log中的數據不一致,這就是臟數據。
即使事務已經被提交了,undo log也不會被立刻刪除,因為需要保證日后事務也可以進行回滾操作。當事務隔離級別為REAPEATABLE READ(可重復讀,即MySQL默認的事務隔離級別)時,事務讀取的都是開啟事務時的最新提交行版本,只要事務不結束,該行版本就不能刪除,即undo logo也不能被刪除。