目錄
7.4 有沒有用到ChangeBuffer對於Redo log的區別
8.3 考慮所有日記和ChangeBuffer 示範--現有Innodb流程
大家都清楚,紀錄檔是 MySQL資料庫的重要組成部分,記錄著資料庫執行期間各種狀態資訊。MySQL紀錄檔主要包括錯誤紀錄檔、查詢紀錄檔、慢查詢紀錄檔、二進位制紀錄檔(binlog)和事務紀錄檔(redo log、undo log)幾大類。
其中,二進位制日記和事務日記尤為重要,一直被人重視、深入研究;可是事實很殘忍,重視或者說大多數人一般都是瞭解個表面,真正懂得人並不多。真想攻破這兩塊日記必須下血本,而且還不一定能攻破。但是不要緊,為了讓你們省下血本還能順利攻破這兩塊日記,我連續研究幾周MySQL日記,最終肝出了這篇文章。
文章指導:文章第三節內容切莫跳過,但如果覺得第四、第五、第六和第七節沒意思或者已經有了概念,直接進入文章第八節一舉攻破拿下。
文章方向:理論、原理篇。
基礎不牢地動山搖,還是常規套路,先把必要知識普及/溫習一遍,當後續文章出現疑慮反過來看下這些概念字典,說不定能 「柳暗花明又一村」 呢?
寫了又寫,想了又想,糾結了好久,這部分知識確實有點多,最後還是決定將這些必要概念字典單獨分出一個文章,後續打算用截圖方式引入各個章節中,建議遇到不懂名詞查閱一下字典。
點選獲取通往字典的大門的鑰匙--想學習進階資料庫知識???這些概念必須攻破!
Binlog 是邏輯日記,用於記錄資料庫執行的寫入操作(查詢不記錄)資訊,Server層記錄和引擎層無關,並且是以追加方式進行寫入,可以通過引數 max_binlog_size
設定每個Binlog檔案的大小,檔案大小達到設定值時會生成新的檔案來儲存日記。
在實際應用中,主要用在兩個場景:主從複製和資料恢復
Binlog何時記錄將在第六點進行介紹,大致記錄過程是先寫Binlog Buffer,然後通過刷盤時機,控制刷入OS Buffer,控制fsync()進行寫入Binlog File日記磁碟的過程。
對於Binlog,MySQL是通過引數sync_binlog引數來控制刷盤時機,取值是0、1和N三種值。0表示由系統自行判斷何時呼叫sync()寫入磁碟;1表示每次事務commit都要呼叫fsync()寫入磁碟;N表示每N個事務,才會呼叫fsync()寫入磁碟。
MySQL5.7.7版本之前預設格式是STATEMENT,版本之後預設是ROW,可以通過引數 binlog-format指定。
STATMENT | 基於SQL語句複製,每一條修改資料的sql語句都會被記錄到Binlog中。 優點:不需要記錄每一行的變化,記錄Binlog日記量減少。 缺點:在某種情況下會導致主從資料不一致,比如執行sysdate()、slepp()等,具體可以自己去深入瞭解。 |
ROW | 推薦使用,MySQL高版本的預設值。基於行的複製,不記錄每條sql資訊,僅僅記錄哪行資料被修改了。 優點:沒有STATMENT模式中所說的主從資料不一致情況。 缺點:記錄行,需要更多的空間,會產生大量的日記。 |
MIXED | 上面兩種模式的混合。一般情況採用STATMENT模式儲存Binlog,特殊情況採用ROW模式儲存Binlog。 |
Undo log是邏輯日記、回滾日記。比如一條修改+3的邏輯語句,Undo log會記錄對應一條-3的邏輯日記,一條插入語句則會記錄一條刪除語句,這樣發生錯誤時,根據執行Undo log就可以回滾到事務之前的資料狀態。
刷盤過程及時機類似於Binlog和Redo,可以參考Redo log刷盤時機章節給出的圖片,已經體現出來了。
Undo log日記內容不是很多,重點是回滾和多版本控制MVCC那塊。此外,我記得印象筆記深刻的是長事務會導致日記過多,這個日記就是Undo log。因為長事務存在,導致需要儲存很多檢視快照,其實這裡就是設及到Undo log何時刪除和生成的問題,當時糾結好久,其實很簡單。生成是事務開始後寫Redo log之前生成,當沒有事務需要用到Undo log時就會被刪除。舉個例子,如果事務A一直存活,那麼事務A之後產生的事務B、C...等等就算提交了,也不會被刪除,因為事務A需要用到B、C...事務去找A的版本。所以避免長事務可以減少Undo log日記量,當然還可以提高效能。
Redo log 是重做日記,屬於InnoDB引擎的日記。是物理日記,日記記錄的內容的是資料頁的更改,這個頁 「做了什麼改動」。如:add xx記錄 to Page1,向資料頁Page1增加一個記錄。
更新記憶體後引擎層寫Redo log將狀態改成prepare為預提交第一階段,Server層寫Binlog,將狀態改成commit為提交第二階段。兩階段提交可以確保Binlog和Redo log資料一致性。
MySQL的處理過程如下
只有在redo log狀態為prepare時,才會去檢查binlog是否存在,否則只校驗redo log是否是 commit就可以啦。 怎麼檢查binlog:一個完整事物binlog結尾有固定的格式。
Undo log的刷盤時機和Redo log差不多,但是對於Undo log我沒找到對應的刷盤引數設計,所以不在提。Redo log每次先寫入Redo Log Buffer中,然後通過刷盤時機控制刷入OS Buffer時間和刷入日記磁碟的時間。
在Undo Log中,MySQL是通過引數innodb_flush_log_at_trx_commit
來控制刷盤時機,取值是0、1和2三種值。0表示事務提交後,每秒寫入OS Buffer並呼叫fsync()寫入日記磁碟中;1表示每次事務提交會寫入OS Buffer並呼叫fsync()將日記寫入日記磁碟中。2表示事務每次提交寫入到OS Buffer,每秒呼叫fsync()寫入日記磁碟。可見引數為1是最安全的,同時也是預設值。
上圖是日記磁碟的Redo log環形設計圖(從頭寫,寫到結束又從頭開始寫~迴圈)。write pos和check point是兩個指標,write pos指標指向當前日記檔案寫入的位置,check point指標指向當前要擦除的開始位置。圖中綠色部分是可以寫入Redo log地方,每次寫入,write pos指標會順時針推進,當然基本不會與check point指標重合,因為MySQL有這種機制去實現,每次觸發檢查點checkpoint,check point會指標向前推進,這個過程就是需要進行刷日記和資料磁碟,記錄相應的LSN,引出難點LSN。
啥時候會觸發檢查點checkpoint,網上找了點資料:啥時候資料庫會觸發檢查點checkpoint
Checkpoint發生的時間、條件及髒頁的選擇等都非常複雜。而Checkpoint所做的事情無外乎是將緩衝池中的髒頁刷回到磁碟,不同之處在於每次重新整理多少頁到磁碟,每次從哪裡取髒頁,以及什麼時間觸發Checkpoint。這些本文不會去研究。
LSN這個概念,比較複雜,我介紹完你們不一定懂!LSN稱為紀錄檔的邏輯序列號(log sequence number),在innodb儲存引擎中,lsn佔用8個位元組。LSN的值會隨著紀錄檔的寫入而逐漸增大。可以簡單理解SLN就是記錄從開始到現在已經產生了多少位元組的Redo log值。
儲存方式兩個指標又是通過LSN計算得到指向位置,因為LSN記錄的是檔案的大小位元組,當超過檔案大小時,需要用取模計算出這兩個指標位置,取模使得寫入就會從頭開始寫,這樣使得兩個指標在一個檔案中,一直落在迴圈位置,你追我趕的過程。這就是Redo log 環形邏輯思想設計實現。
上面提到LSN比較複雜,是因為它有很多個值,輸入命令"show engine innodb status;",可以看到四個的lsn記錄
為了方便識別,我都為它們重新命名,如下。名詞記不住,後面無法繼續深入
1、記憶體日記:redo log buffer lsn;磁碟日記:redo log file lsn;
一般關係為:redo log buffer lsn >= redo log file lsn,如果刷盤時機為1,則redo log buffer lsn = redo log file lsn。
2、記憶體資料頁:data buffer lsn;資料磁碟資料頁:data disk lsn;
一般關係為data buffer lsn > data disk lsn,如果已經刷入資料磁碟,則data buffer lsn = data disk lsn。
3、檢查點:chckpoint lsn;
後面提到檢查點刷盤,資料刷盤和日記刷盤(如果有日記刷盤:則說明我假設的日記刷盤的時機設定值不為1,為1是同步的,即始終redo log buffer lsn = redo log file lsn,不會由檢查點觸發刷日記磁碟)。
都說Redo log是環形記錄,那麼怎麼記錄的?下面結合LSN給出記錄過程虛構圖,可以對比6.6 Redo log 儲存方式圖
相關知識:日記磁碟 + redo log file lsn + checkpoint lsn + 雙指標(write pos、check point)
1-8按時間順序發生。1點是假設最初的狀態;2、3點寫日記磁碟;4點是觸發了檢查點checkpoint,進行刷盤,checkpoint lsn=1開始,刷盤結束並更新checkpoint lsn=512。在5點、6點已經刷過了一回圈記憶體、二回圈記憶體,從頭開始寫入log,兩個指標指向回到了頭部。第7點也是一個觸發checkpoint的過程。9點是假設沒有更新,最後達到平衡的結果,即記憶體中資料頁和日記都完成了刷盤。
整個流程:
在某些情況下,觸發checkpoint,觸發資料頁和紀錄檔頁刷盤,此時將記憶體中的髒資料---"資料髒頁"和"紀錄檔髒資料" 分別刷到資料磁碟和日記磁碟中,而且兩者刷盤速度不一樣。checkpoint會保護機制,當資料刷盤速度超過紀錄檔刷盤時,將會暫時停止資料刷盤,等待紀錄檔刷盤進度超過資料刷盤。
刷盤時,對於資料磁碟,全部都是在記憶體中,此時每次刷一個資料頁到記憶體更新資料頁也更新了data disk lsn為data buffer lsn(在更新記憶體資料頁時,會更新data buffer lsn)。
對於日記磁碟,除了要記錄checkpoint lsn的值為檢查點 checkpoint的值(必須在結束時 直接記錄一個值,速度很快),這裡是針對日記刷盤時機不是1(1是同步快取刷日記刷盤)時,並且日記還沒刷到日記磁碟需要觸發將快取中日記提前刷到日記磁碟中,此時會將redo buffer log刷到redo log file中也更新了redo log file lsn為redo log buffer lsn 。
模擬檢查點觸發前後,整個流程變化,一個資料頁和日記,資料變化及lsn從179-180的變化圖(刷盤時機不為1)
結合6.4 Redo log 容災恢復過程和6.8的LSN知識,再次細化6.4的Redo log恢復過程
重新啟動innodb時,Redo log完不完整,採用6.4知識過程。用Redo log恢復,啟動資料庫時,InnoDB會掃描資料磁碟的資料頁data disk lsn和紀錄檔磁碟中的checkpoint lsn。兩者相等則從checkpoint lsn點開始恢復,恢復過程是利用 redo log到buffer pool,直到checkpoint lsn等於redo log file lsn,則恢復完成。
如果checkpoint lsn 小於 data disk lsn,說明在檢查點觸發後還沒結束刷盤時資料庫宕機了。因為checkpoint lsn最新值是在資料刷盤結束後才記錄的,檢查點之後有一部分資料已經刷入資料磁碟,這個時候資料磁碟已經寫入部分的部分恢復將不會重做,直接跳到沒有恢復的lsn值開始恢復。
為啥本文我會提到ChangeBuffer呢,其實很多時候會將ChangeBuffer和Redo log搞混,兩者都是巧用記憶體,減少磁碟IO,為了不弄混我覺得有必要專門對這個進行一個講解。
下面是我對ChangeBuffer的簡單介紹
也就是說對於更新的操作,如果用到了ChangeBuffer,更新的資料所在的資料頁如果不在記憶體中,將不用去資料磁碟將資料頁讀到記憶體,而是將這一次操作記錄在ChangeBuffer中,ChangeBuffer 主要節省的則是隨機讀磁碟的 IO 消耗,下次讀取查詢等讀取資料頁時用上ChangeBuffer中的記錄即可。其實也是一種巧用記憶體的思想。
Redo log 主要節省的是隨機寫磁碟的 IO 消耗(轉成順序寫),而 ChangeBuffer 主要節省的則是隨機讀磁碟的 IO 消耗。
這句話怎麼理解,看下面:
Redo log 與 ChangeBuffer(含磁碟持久化) 這2個機制,不同之處在於優化了整個變更流程的不同階段。
先不考慮Redo log、ChangeBuffer機制,簡化抽象一個更新(insert、update、delete)流程:
其中,流程中的步驟1涉及隨機讀磁碟IO;步驟3涉及隨機寫磁碟IO;剛好對應ChangeBuffer和Redo log。
對那句話的理解答案:
Redo log機制,為了保證crash-safe,一直都會用到。 有無用到ChangeBuffer機制,對於redo log這步的區別在於—— 用到了ChangeBuffer機制時,在Redo log中記錄的本次變更,是記錄new change buffer item相關的資訊,而不是直接的記錄物理頁的變更(文章中第八節都有體現這一過程)。 在我們mysql innodb中, ChangeBuffer機制不是一直會被應用到,僅當待操作的資料頁當前不在記憶體中,需要先讀磁碟載入資料頁時,ChangeBuffer才有用武之地。
除了存取這個資料頁會觸發 merge 外,系統有後臺執行緒會定期 merge。在資料庫正常關閉(shutdown)的過程中,也會執行 merge 操作。
merge過程做三步
前面分別講的是Binlog、Undo log和Redo log,下面將他們都串聯起來,在一些流程體現全部日記。
同樣,以一些最經典的更新語句例子展開說明。
測試語句:插入語句+查詢語句,a欄位是普通索引
1、insert into ta(a,b) values(2,5),(7, 5)
2、select * from t where a in (2, 7)
假設原來的資料如下圖,資料頁page1在記憶體中,page2不在。插入的資料(2,5)落在page1,資料(7,5)落在page2中。
先不考慮所有日記及ChangeBuffer機制,簡化抽象一個更新insert流程
過程是 兩階段提交-----日記刷盤------資料刷盤(涉及Redo log lsn 和 ChangeBuffer的內容)
緊接著上文,圖片可上下參考,假設現在執行查詢語句 「select * from t where a in (2, 7)」 ,此次查詢索引a=7所在的資料頁不在記憶體中,並且上一步更新已經在change buffer中有記錄,將會觸發merge過程(參考第七章節7.5)。
至於changebuffer被應用後是刪除還是標記,還有redo中原有的記錄changebuffer的改動怎麼調整是刪除還是修改成資料頁的改動這裡下面的圖是按照自己的想法描述出來,如有誤望留言指正。
資料刷盤flush的有四種情況
資料刷盤也代表著Redo log檢查點checkpoint觸發,這一步將聯絡到第六章6.7和6.8中的章節內容,較為複雜。
假設資料刷盤flush的四種情況發生了一種,那麼聯絡上文的過程將如下
流程中間某個環節資料庫宕機後,恢復具體過程,這些留在心裡了,沒往上去寫,讀者可以自行思考,不難。
整個文章講了Binlog、Undo log和Redo log,隨帶一提ChangeBuffer,前面四五六七章是分講,最後第八章是對整個日記相關聯講解。對此,講到這裡,基本上要把我要將的已經講完,內容挺多,有耐心可以慢慢啃,不懂歡迎留言!
思考環節,下面留下兩個問題,歡迎大家留言解答
1、為啥Binlog沒有crash-safe功能?
2、保證crash-safe為啥要用兩個日記,不能用一個日記嗎(Redo log或Binglog)?