寫在前面
本文是對于網(wǎng)上各個(gè)對redo和undo log日志解析的總結(jié),參考文章列表在最后卫旱。
事務(wù)的4大特性:原子性惭每、一致性骨饿、隔離性和持久性亏栈。
事務(wù)的隔離性由鎖機(jī)制實(shí)現(xiàn)。
原子性宏赘、一致性和持久性由事務(wù)的redo 日志和undo 日志來保證绒北。
redo log:物理日志,記錄的是數(shù)據(jù)頁的物理修改察署,而不是某一行或某幾行修改成什么樣子闷游,它用來恢復(fù)提交后的物理數(shù)據(jù)頁(恢復(fù)數(shù)據(jù)頁,且只能恢復(fù)到最后一次提交的位置)贴汪。
undo log:用來回滾行記錄到某個(gè)版本脐往。undo log一般是邏輯日志,根據(jù)每行數(shù)據(jù)的修改前數(shù)據(jù)和修改操作記錄扳埂。
redo log
redo log和二進(jìn)制日志的區(qū)別
二進(jìn)制日志相關(guān)內(nèi)容业簿,參考:MariaDB/MySQL的二進(jìn)制日志 。
redo log不是二進(jìn)制日志阳懂。雖然二進(jìn)制日志中也記錄了innodb表的很多操作梅尤,也能實(shí)現(xiàn)重做的功能,但是它們之間有很大區(qū)別岩调。
- 二進(jìn)制日志是在存儲引擎的上層產(chǎn)生的巷燥,不管是什么存儲引擎,對數(shù)據(jù)庫進(jìn)行了修改都會產(chǎn)生二進(jìn)制日志誊辉。而redo log是innodb層產(chǎn)生的矾湃,只記錄該存儲引擎中表的修改。并且二進(jìn)制日志先于****redo log****被記錄堕澄。
- 二進(jìn)制日志記錄邏輯性的語句邀跃。即便它是基于行格式的記錄方式,其本質(zhì)也還是邏輯的SQL設(shè)置蛙紫,如該行記錄的每列的值是多少拍屑。而redo log是在物理格式上的日志,它記錄的是數(shù)據(jù)庫中每個(gè)頁的修改坑傅。
- 二進(jìn)制日志只在每次事務(wù)提交的時(shí)候一次性寫入緩存中的日志"文件"僵驰。而redo log在事務(wù)中就會產(chǎn)生,并且在對數(shù)據(jù)真正修改前先寫入緩存中的redo log中唁毒,然后才對內(nèi)存中的數(shù)據(jù)執(zhí)行修改操作蒜茴;而且保證在發(fā)出事務(wù)提交指令時(shí),先向緩存中的redo log寫入到redo log file中浆西,redo log file 寫入完成后才執(zhí)行提交動作粉私。
- 因?yàn)槎M(jìn)制日志只在提交的時(shí)候一次性寫入,所以二進(jìn)制日志中的記錄方式和提交順序有關(guān)近零,且一次提交對應(yīng)一次記錄诺核。而redo log中是記錄的物理頁的修改抄肖,redo log file中同一個(gè)事務(wù)可能多次記錄,最后一個(gè)提交的事務(wù)記錄會覆蓋所有未提交的事務(wù)記錄窖杀。例如事務(wù)T1漓摩,可能在redo log中記錄了 T1-1,T1- 2,T1-3,T1* 共4個(gè)操作入客,其中 T1* 表示最后提交時(shí)的日志記錄管毙,所以對應(yīng)的數(shù)據(jù)頁最終狀態(tài)是 T1* 對應(yīng)的操作結(jié)果。而且redo log是并發(fā)寫入的痊项,不同事務(wù)之間的不同版本的記錄會穿插寫入到redo log文件中锅风,例如可能redo log的記錄方式如下: T1-1,T1-2,T2-1,T2- 2,T2,T1-3,T1 。
Redo 的類型
重做日志(redo log)用來保證事務(wù)的持久性鞍泉,即事務(wù)ACID中的D。實(shí)際上它可以分為以下兩種類型:
- 物理Redo日志
- 邏輯Redo日志
在InnoDB存儲引擎中肮帐,大部分情況下 Redo是物理日志咖驮,記錄的是數(shù)據(jù)頁的物理變化。
邏輯Redo日志训枢,不記錄頁面的實(shí)際修改內(nèi)容托修,而只記錄修改頁面的一類操作,比如新建數(shù)據(jù)頁恒界。關(guān)于邏輯Redo日志涉及更加底層的內(nèi)容睦刃,這里我們只需要記住絕大數(shù)情況下,Redo是物理日志即可十酣,DML對頁的修改操作涩拙,均需要記錄Redo。
Redo 的作用
Redo log的主要作用是用于數(shù)據(jù)庫的崩潰恢復(fù)耸采。
Redo 的組成
Redo log分為兩部分:
- redo log buffer:在內(nèi)存中,易失兴泥。
- redo log file:在磁盤中,是持久的虾宇。
什么時(shí)候?qū)慠edo log?
上面那張圖簡單地體現(xiàn)了Redo的寫入流程搓彻,這里再細(xì)說下寫入Redo的時(shí)機(jī):
- 在數(shù)據(jù)頁修改完成之后,在臟頁刷出磁盤之前嘱朽,寫入redo日志旭贬。注意的是先修改數(shù)據(jù),后寫日志
- redo日志比數(shù)據(jù)頁先寫回磁盤
- 聚集索引搪泳、二級索引稀轨、undo頁面的修改,均需要記錄Redo日志森书。
Redo的整體流程
下面以一個(gè)更新事務(wù)為例靶端,宏觀執(zhí)行流程如下圖所示:
- 1:將原始數(shù)據(jù)從磁盤中讀入內(nèi)存中谎势,修改數(shù)據(jù)在內(nèi)存中的拷貝
- 2:生成一條redo log并寫入redo log buffer,記錄的是數(shù)據(jù)被修改后的值
- 3:當(dāng)事務(wù)commit時(shí)杨名,將redo log buffer中的內(nèi)容刷新到 redo log file脏榆,對 redo log file采用追加寫的方式
- 4:定期將內(nèi)存中修改的數(shù)據(jù)刷新到磁盤中
redo如何保證事務(wù)的持久性?
InnoDB是事務(wù)的存儲引擎台谍,其通過Force Log at Commit 機(jī)制實(shí)現(xiàn)事務(wù)的持久性须喂。
即當(dāng)事務(wù)提交時(shí),先將 redo log buffer 寫入到 redo log file 進(jìn)行持久化趁蕊,待事務(wù)的commit操作完成時(shí)才算完成坞生。這種做法也被稱為 Write-Ahead Log(預(yù)先日志持久化):在持久化一個(gè)數(shù)據(jù)頁之前,先將內(nèi)存中相應(yīng)的日志頁持久化掷伙。
為了保證每次日志都寫入redo log file是己,在每次將redo buffer寫入redo log file之后,默認(rèn)情況下任柜,InnoDB存儲引擎都需要調(diào)用一次 fsync操作,因?yàn)閞edo log并沒有 O_DIRECT選項(xiàng)卒废,所以redo log先寫入到文件系統(tǒng)緩存。為了確保redo log寫入到磁盤宙地,必須進(jìn)行一次 fsync操作摔认。fsync是一種系統(tǒng)調(diào)用操作,其fsync的效率取決于磁盤的性能宅粥,因此磁盤的性能也影響了事務(wù)提交的性能参袱,也就是數(shù)據(jù)庫的性能。
(O_DIRECT選項(xiàng)是在Linux系統(tǒng)中的選項(xiàng)秽梅,使用該選項(xiàng)后抹蚀,對文件進(jìn)行直接IO操作,不經(jīng)過文件系統(tǒng)緩存风纠,直接寫入磁盤)
上面提到的Force Log at Commit機(jī)制就是靠InnoDB存儲引擎提供的參數(shù) innodb_flush_log_at_trx_commit
來控制的况鸣,該參數(shù)可以控制 redo log刷新到磁盤的策略,設(shè)置該參數(shù)值也可以允許用戶設(shè)置非持久性的情況發(fā)生竹观,具體如下:
- 當(dāng)設(shè)置參數(shù)為1時(shí)镐捧,(默認(rèn)為1),表示事務(wù)提交時(shí)必須調(diào)用一次
fsync
操作臭增,最安全的配置懂酱,保障持久性。 - 當(dāng)設(shè)置參數(shù)為2時(shí)誊抛,則在事務(wù)提交時(shí)只做 write 操作列牺,只保證將redo log buffer寫到系統(tǒng)的頁面緩存中,不進(jìn)行fsync操作拗窃,因此如果MySQL數(shù)據(jù)庫宕機(jī)時(shí) 不會丟失事務(wù)瞎领,但操作系統(tǒng)宕機(jī)則可能丟失事務(wù)泌辫。
-
當(dāng)設(shè)置參數(shù)為0時(shí),表示事務(wù)提交時(shí)不進(jìn)行寫入redo log file操作九默,這個(gè)操作僅在master thread 中完成震放,而在master thread中每1秒進(jìn)行一次重做日志的fsync操作,因此實(shí)例 crash 最多丟失1秒鐘內(nèi)的事務(wù)驼修。(master thread是負(fù)責(zé)將緩沖池中的數(shù)據(jù)異步刷新到磁盤殿遂,保證數(shù)據(jù)的一致性)
具體過程如下:
fsync
和write
操作實(shí)際上是系統(tǒng)調(diào)用函數(shù),在很多持久化場景都有使用到乙各,比如 Redis 的AOF持久化中也使用到兩個(gè)函數(shù)墨礁。fsync
操作將數(shù)據(jù)提交到硬盤中,強(qiáng)制硬盤同步耳峦,將一直阻塞到寫入硬盤完成后返回恩静,大量進(jìn)行fsync
操作就有性能瓶頸,而write
操作將數(shù)據(jù)寫到系統(tǒng)的頁面緩存后立即返回蹲坷,后面依靠系統(tǒng)的調(diào)度機(jī)制將緩存數(shù)據(jù)刷到磁盤中去,其順序是user buffer——> page cache——>disk蜕企。
除了上面談到的Force Log at Commit機(jī)制保證事務(wù)的持久性,實(shí)際上redo log的實(shí)現(xiàn)還要依賴于mini-transaction冠句。
Redo在InnoDB中是如何實(shí)現(xiàn)的?與mini-transaction的聯(lián)系幸乒?
Redo log的實(shí)現(xiàn)跟mini-transaction緊密相關(guān)懦底,mini-transaction是InnoDB內(nèi)部的機(jī)制,通過mini-transaction來保證并發(fā)事務(wù)操作下以及數(shù)據(jù)庫異常時(shí)數(shù)據(jù)頁中數(shù)據(jù)的一致性罕扎,但它不屬于事務(wù)聚唐。
為了使得mini-transaction保證數(shù)據(jù)頁數(shù)據(jù)的一致性,mini-transaction必須遵循以下三種協(xié)議:
- The FIX Rules
- Write-Ahead Log
- Force-log-at-commit
The FIX Rules
修改一個(gè)數(shù)據(jù)頁時(shí)需要獲得該頁的x-latch(排他鎖)腔召,獲取一個(gè)數(shù)據(jù)頁時(shí)需要該頁的s-latch(讀鎖或者稱為共享鎖) 或者是 x-latch杆查,持有該頁的鎖直到修改或訪問該頁的操作完成。
Write-Ahead Log
在持久化一個(gè)數(shù)據(jù)頁之前臀蛛,必須先將內(nèi)存中相應(yīng)的日志頁持久化亲桦。每個(gè)頁都有一個(gè)LSN(log sequence number),代表日志序列號浊仆,(LSN占用8字節(jié)客峭,單調(diào)遞增), 當(dāng)一個(gè)數(shù)據(jù)頁需要寫入到持久化設(shè)備之前,要求內(nèi)存中小于該頁LSN的日志先寫入持久化設(shè)備抡柿。
那為什么必須要先寫日志呢舔琅?可不可以不寫日志,直接將數(shù)據(jù)寫入磁盤洲劣?原則上是可以的备蚓,只不過會產(chǎn)生一些問題课蔬,數(shù)據(jù)修改會產(chǎn)生隨機(jī)IO,但日志是順序IO郊尝,append方式順序?qū)懚希且环N串行的方式,這樣才能充分利用磁盤的性能虚循。
Force-log-at-commit
在一個(gè)事務(wù)中可以修改多個(gè)頁同欠,Write-Ahead Log 可以保證單個(gè)數(shù)據(jù)頁的一致性,但是無法保證事務(wù)的持久性横缔,F(xiàn)orce-log-at-commit 要求當(dāng)一個(gè)事務(wù)提交時(shí)铺遂,其產(chǎn)生所有的mini-transaction 日志必須刷新到磁盤中,若日志刷新完成后茎刚,在緩沖池中的頁刷新到持久化存儲設(shè)備前數(shù)據(jù)庫發(fā)生了宕機(jī)襟锐,那么數(shù)據(jù)庫重啟時(shí),可以通過日志來保證數(shù)據(jù)的完整性膛锭。
redo log的寫入流程
上圖表示了redo log的寫入流程粮坞,每個(gè)mini-transaction對應(yīng)每一條DML操作,比如一條update語句初狰,其由一個(gè)mini-transaction來保證莫杈,對數(shù)據(jù)修改后,產(chǎn)生redo1奢入,首先將其寫入mini-transaction私有的Buffer中筝闹,update語句結(jié)束后,將redo1從私有Buffer拷貝到公有的Log Buffer中腥光。當(dāng)整個(gè)外部事務(wù)提交時(shí)关顷,將redo log buffer再刷入到redo log file中。
undo log
undo log的定義
undo log主要記錄數(shù)據(jù)的邏輯變化武福,為了在發(fā)生錯(cuò)誤時(shí)回滾之前的操作议双,需要將之前的操作都記錄下來,然后在發(fā)生錯(cuò)誤時(shí)才可以回滾捉片。
undo log的作用
undo是一種邏輯日志平痰,有兩個(gè)作用:
- 事務(wù)回滾
- MVCC
重點(diǎn)關(guān)注如何利用undo log進(jìn)行事務(wù)回滾。
undo日志界睁,只將數(shù)據(jù)庫邏輯地恢復(fù)到原來的樣子觉增,在回滾的時(shí)候,它實(shí)際上是做的相反的工作翻斟,比如一條INSERT 逾礁,對應(yīng)一條 DELETE,對于每個(gè)UPDATE,對應(yīng)一條相反的 UPDATE,將修改前的行放回去。通過undo log進(jìn)行事務(wù)回滾操作可以保障事務(wù)的原子性嘹履。
undo log的寫入時(shí)機(jī)
- DML操作修改聚簇索引前腻扇,記錄undo log
- 二級索引記錄的修改,不記錄undo log
需要注意的是砾嫉,undo log頁面的修改幼苛,同樣需要記錄redo日志。
undo log的存儲位置
在InnoDB存儲引擎中焕刮,undo log存儲在回滾段(Rollback Segment)中,每個(gè)回滾段記錄了1024個(gè)undo log segment舶沿,而在每個(gè)undo log segment段中進(jìn)行undo 頁的申請,在5.6以前配并,Rollback Segment是在共享表空間里的括荡,5.6.3之后,可通過 innodb_undo_tablespace設(shè)置undo存儲的位置溉旋。
undo的類型
在InnoDB存儲引擎中畸冲,undo log分為:
- insert undo log
- update undo log
insert undo log是指在insert 操作中產(chǎn)生的undo log,因?yàn)閕nsert操作的記錄观腊,只對事務(wù)本身可見邑闲,對其他事務(wù)不可見。故該undo log可以在事務(wù)提交后直接刪除梧油,不需要進(jìn)行purge操作苫耸。
而update undo log記錄的是對delete 和update操作產(chǎn)生的undo log,該undo log可能需要提供MVCC機(jī)制儡陨,因此不能再事務(wù)提交時(shí)就進(jìn)行刪除鲸阔。提交時(shí)放入undo log鏈表,等待purge線程進(jìn)行最后的刪除迄委。
補(bǔ)充:purge線程兩個(gè)主要作用是:清理undo頁和清除page里面帶有Delete_Bit標(biāo)識的數(shù)據(jù)行。在InnoDB中类少,事務(wù)中的Delete操作實(shí)際上并不是真正的刪除掉數(shù)據(jù)行叙身,而是一種Delete Mark操作,在記錄上標(biāo)識Delete_Bit硫狞,而不刪除記錄信轿。是一種"假刪除",只是做了個(gè)標(biāo)記,真正的刪除工作需要后臺purge線程去完成残吩。
undo log 是否是redo log的逆過程财忽?
undo log 是否是redo log的逆過程?其實(shí)從前文就可以得出答案了泣侮,undo log是邏輯日志即彪,對事務(wù)回滾時(shí),只是將數(shù)據(jù)庫邏輯地恢復(fù)到原來的樣子活尊,而redo log是物理日志隶校,記錄的是數(shù)據(jù)頁的物理變化漏益,顯然undo log不是redo log的逆過程。
redo & undo總結(jié)
下面是redo log + undo log的簡化過程深胳,便于理解兩種日志的過程:
假設(shè)有A绰疤、B兩個(gè)數(shù)據(jù),值分別為1,2.
1. 事務(wù)開始
2. 記錄A=1到undo log
3. 修改A=3
4. 記錄A=3到 redo log
5. 記錄B=2到 undo log
6. 修改B=4
7. 記錄B=4到redo log
8. 將redo log寫入磁盤
9. 事務(wù)提交
實(shí)際上舞终,在insert/update/delete操作中轻庆,redo和undo分別記錄的內(nèi)容都不一樣,量也不一樣敛劝。在InnoDB內(nèi)存中余爆,一般的順序如下:
- 寫undo的redo
- 寫undo
- 修改數(shù)據(jù)頁
- 寫Redo
參考:
https://segmentfault.com/a/1190000017888478
https://juejin.im/entry/5ba0a254e51d450e735e4a1f