MySQL,在線熱備的內核原理!
研發的童鞋每次對MySQL庫表做重大操作之前,例如:
- 修改表結構;
- 批量修改或者刪除數據;
都會向DBA申請進行數據庫的備份。
畫外音:又或者說,不備份直接操作啦?
那DBA童鞋是怎么進行MySQL備份的呢?
調研了幾十個RD和QA,基本是3種答案:
- 不太清楚;
- 在線邏輯備份,mysqldump;
- 離線物理備份(冷備),拷貝從庫庫文件;
那實際上,DBA是如何對MySQL進行庫備份的呢?
現在基本上使用的是PXB方案。
今天,和大家說說MySQL備份的來龍去脈,以及內核原理。
在線邏輯備份,mysqldump是咋回事?
mysqldump是MySQL工具集中的一個工具,可以用來導出或備份數據。
mysqldump的產出物是一個包含了建表,插入數據的SQL語句集合,類似于這樣:
- -- MySQL dump 1.2.3
- -- Host: localhost Database: test
- -- Server version 4.5.6
- CREATE TABLE t_user (
- id int(11)NOT NULL unique,
- name varchar(40) NOT NULL default '',
- PRIMARY KEY (id)
- );
- INSERT INTO t_user VALUES (1,'shenjian');
- INSERT INTO t_user VALUES (2,'zhangsan');
- INSERT INTO t_user VALUES (3,'lisi');
因此,它才稱為邏輯備份。
使用mysqldump進行備份的優點是:可以在線進行,不影響數據庫對線上持續提供服務。
缺點也顯而易見:相比物理備份拷貝庫文件,備份和恢復都要慢非常多。
離線物理備份,拷貝從庫庫文件又是咋回事?
為了提高備份效率,縮短備份時間,這也就引發了第二種方案,直接物理備份庫文件。
如上圖所示,數據庫集群設置為左側的1主2從架構,離線物理備份是如何實施的呢?
- 第一步,將一個從庫從集群里摘下并下線,此時離線庫文件不會再發生變化;
- 第二步,scp拷貝庫文件,即完成了庫的物理備份;
- 文件拷貝完成后,將從庫掛回集群。
使用離線物理備份的優點是:備份和恢復都非常快。
缺點也顯而易見:備份過程中從庫無法對線上持續提供服務。
那么問題來了,有沒有一種方案,又能夠快速備份物理文件,又能夠持續對線上提供服務呢?這就是如今MySQL備份最流行的PXB方案。
什么是PXB?
PXB的全稱是,Percona XtraBackup,官網是這么吹的:PXB是全世界唯一一款開源免費的,支持MySQL熱備的,非阻塞備份工具。
畫外音:Percona XtraBackup is the world’s only open-source, free MySQL hotbackup software that performs non-blocking backups tool.
那么,PXB是如何實現:
- 保持數據庫持續提供線上服務,庫文件不斷變化時;
- 通過MySQL文件;
- 來進行庫文件物理熱備份的呢?
為了把問題講透,這就要從redo log,從LSN,從MySQL的故障恢復(crash-recovery)機制聊起。
一、redo log
為什么要有redo log?
事務提交后,必須將事務對數據頁的修改刷(fsync)到磁盤上,才能保證事務的ACID特性。
這個刷盤,是一個隨機寫,隨機寫性能較低,如果每次事務提交都刷盤,會極大影響數據庫的性能。
隨機寫性能差,有什么優化方法呢?
架構設計中有兩個常見的優化方法:
- 先寫日志(write log first),將隨機寫優化為順序寫;
- 將每次寫優化為批量寫;
這兩個優化,數據庫都用上了。
第一個優化,將對數據的修改先順序寫到日志里,這個日志就是redo log。
第二個優化,就是redo log的三層架構:
- log buffer:應用層緩沖;
- OS cache:操作系統緩存;
- redo log file:物理文件;畫外音:此處不是本文的重點,不再展開詳述。
假如某一時刻,數據庫崩潰,還沒來得及將數據頁刷盤,數據庫重啟時,會重做redo log里的內容,以保證已提交事務對數據的影響被刷到磁盤上。
一句話,redo log是為了保證已提交事務的ACID特性,同時能夠提高數據庫性能的技術。
二、redo log的格式
邏輯上,MySQL以行(row)為單位管理數據;物理上,MySQL以頁(page)為單位管理數據,MySQL的緩沖池(buffer)機制,也是以頁為單位管理數據,事務提交之后,不用每次都隨機寫落盤刷新數據頁,而是通過順序寫redo log來提高性能,那么redo log是直接保存等待刷盤的數據頁嗎?
如果redo log直接保存待刷盤的數據頁,存在這樣的問題,假如某個SQL語句只修改了一行記錄里的一個屬性,例如:
- update set sex=1 where name='shenjian'
物理上,其實只修改了1個字節,難道redo log要將這個屬性所在的一頁數據(16K)全部保存下來嗎?
完全不用,redo log只需要記錄:
- 某個數據頁中(page num);
- 某個某個偏移位置(offset);
- 某個類型的數據(type);
- 改成了什么值(value);
如此一來,redo log既能夠實現以頁為單位順序刷盤數據,又極大縮小了日志大小,其性能又進一步的增加了。
- update set sex=1 where name='shenjian'
仍以這個SQL為例,假設它修改了第1234頁,偏移量為5678處,1個字節的數據,這個字節的sex由0改成了1,那么,很容易想到redo log是類似于這樣的一個結構:
如此一來,當數據庫崩潰的時候,如果緩沖池中的數據沒有來得及刷盤,就可以通過redo log,把第1234頁,偏移量為5678處的1個字節改為1,以此來恢復數據。
當然,MySQL會通過一系列的數據結構對redo log來進行管理,最小單位的redo log是一個512字節的數據塊(block),這個數塊由12字節的header,508字節的body,4字節的trailer組成,body里保存的就是上述數據頁如何進行修改的記錄。
記錄redo log的文件有若干個,每個都固定大小,循環使用。
畫外音:為了使得行文通俗易懂,本文盡量沒有提及Mini-Transaction(mtr)的概念。
三、LSN
要聊redo log,要聊故障恢復,LSN是一個繞不開的概念。
什么是LSN?
LSN,Log Sequeue Number,直譯過來叫日志序列號,是InnoDB中,隨著日志的寫入,一個只增不減的8字節序列號。
聽上去叫日志序列號,但LSN并不只存在redo log中,它還存儲在數據頁里。
畫外音:緩沖池中的數據頁,磁盤上的數據頁都存儲了LSN。
數據頁(page)里存儲的LSN,可以用來標記數據頁的“版本號”,記錄該數據頁最后一次被修改的日志序列的位置。
舉個例子,假設邏輯上連續執行了兩個事物,且都已經提交:
- trx1:
- update set sex=0 where name='shenjian'
- redolog lsn=1000
- trx2:
- update set sex=1 where name='shenjian'
- redolog lsn=1001
畫外音:lsn增加了。
又假設,第一個事務trx1已經刷盤,而第二個事務trx2還沒有刷盤,只寫了redo log。畫外音:最近一次刷盤的頁,即最近一次檢查點(checkpoint),也是通過LSN來記錄的,它也會被寫入redo log里。
這兩個事務修改的是同一個數據頁,很容易想到:磁盤數據頁上的LSN=1000
而redo log里有兩條記錄:
- 第一條,redo log lsn=1000
- 第二條,redo log lsn=1001
為了提高數據庫性能,數據庫基本都是使用WAL(Write Ahead Log)的方式,先寫日志再刷盤,所以很容易能夠想到,磁盤數據頁里的LSN,會小于最新redo log中的LSN。
畫外音:此時,redo log中記錄的checkpoint也是1000。
LSN有什么用呢?
它和MySQL的故障恢復(crash-recovery)機制緊密相關。
四、InnoDB故障恢復(crash-recovery)
這里的故障恢復,是指MySQL非正常退出,然后再次啟動之前,要恢復數據一致性的操作。
畫外音:可能直譯叫崩潰恢復更準確一些。
InnoDB的崩潰恢復過程是怎么樣的?
主要分為四個步驟:
第一步,redo log操作:保證已提交事務影響的最新數據刷到數據頁里。
第二步,undo log操作:保證未提交事務影響的數據頁回滾。
第三步,寫緩沖(change buffer)合并。
畫外音:不是今天的重點,關于寫緩沖的概念,詳見《寫緩沖(change buffer),這次徹底懂了!》。
第四步,purge操作。
畫外音:InnoDB的一種垃圾收集機制,使用單獨的后臺線程周期性處理索引中標記刪除的數據,也不是今天的重點,未來可以詳細講。
第一個步驟中,redo log操作是如何恢復最新的數據頁的呢?
(1)從redo log中讀取checkpoint lsn,它記錄的是最后一次刷盤的頁,對應日志的LSN;
(2)如果redo log中記錄的日志LSN小于checkpoint,說明相關數據已經被刷盤,不用額外操作;
(3)如果redo log中記錄的日志LSN大于checkpoint,說明相關數據只寫了redo log,沒來得及刷盤,就需要對相關數據頁重做日志,例如:
將第1234頁,偏移量為5678處的1個字節改為1,以此來恢復數據。
崩潰恢復過程中,MySQL的啟動日志更形象的說明了這一點:
- 先找到checkpoint;
- 然后不斷的掃描大于checkpoint的redo log,不斷的恢復數據;
畫外音:redo log的LSN可以看到恢復的進程。
多說一句,redo log還有兩個特性:
- 第一,冪等性,同一條redo log執行多次,不影響數據的恢復。
- 第二,崩潰恢復時,從比checkpoint更早的LSN開始執行恢復,也不影響數據最終的一致性,因為一個數據頁,最終一定會被更大值的LSN日志恢復到最新的數據上來。
五、PXB在線熱備原理
不知不覺寫了幾千字,差點忘了緣起的問題。
PXB是如何實現:
- 保持數據庫持續提供線上服務,庫文件不斷變化時;
- 通過MySQL文件;
- 來進行庫文件物理熱備份的呢?
通過上面大把的鋪墊,這個問題的回答就容易了。
首先,PXB啟動一個線程,并不斷監聽并復制redo log的增量到另外的文件,不能直接備份redo log的原因是,redo log循環使用的,PXB則必須記錄下checkpoint LSN之后的所有redo log。
然后,PXB啟動另一個線程,然后開始復制數據文件,復制數據文件過程可能會比較長,整個過程中數據文件可能在不停的修改,導致數據不一致。但沒有關系,所有的修改都已經記錄在了第一步中,額外記錄的redo log里。
畫外音:務必注意,備份redo log的線程,必須在開始備份數據文件之前啟動,之后結束。
最后,通過備份的數據文件,重放redo log,執行類似于MySQL崩潰恢復過程中的動作,就能夠使得數據文件恢復到能保證一致性的checkpoint檢查點。
畫外音:PXB還可以對非MySQL,非InnoDB進行在線熱備,這里就不展開了。
是不是很神奇啊!
【本文為51CTO專欄作者“58沈劍”原創稿件,轉載請聯系原作者】