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