事務(wù)的定義
事務(wù)的基本要素(ACID)
原子性:Atomicity匀油,整個數(shù)據(jù)庫事務(wù)是不可分割的工作單位
一致性:Consistency敌蚜,事務(wù)將數(shù)據(jù)庫從一種狀態(tài)轉(zhuǎn)變?yōu)橄乱环N一致的狀態(tài)
隔離性:Isolation齐媒,每個讀寫事務(wù)的對象對其他事務(wù)的操作對象能相互分離
持久性:Durability喻括,事務(wù)一旦提交唬血,其結(jié)果是永久性的
事務(wù)的并發(fā)問題
臟讀:事務(wù)A讀取了事務(wù)B更新的數(shù)據(jù)拷恨,然后B回滾操作腕侄,那么A讀取到的數(shù)據(jù)是臟數(shù)據(jù)
不可重復(fù)讀:事務(wù) A 多次讀取同一數(shù)據(jù),期間事務(wù) B對數(shù)據(jù)作了更新并提交酸茴,導(dǎo)致事務(wù)A多次讀取同一數(shù)據(jù)時噪舀,結(jié)果 不一致。
幻讀:事務(wù) A 多次同一條件查詢界逛,期間事務(wù)B刪除或插入了滿足條件的數(shù)據(jù)息拜,導(dǎo)致事務(wù)A多次讀取的結(jié)果集不一致。
SQL標(biāo)準(zhǔn)定義的隔離級別為:
事務(wù)隔離級別 | 描述 | 臟讀 | 不可重復(fù)讀 | 幻讀 |
---|---|---|---|---|
READ UNCOMMITTED | 一個事務(wù)會讀到另一個未提交事務(wù)修改過的數(shù)據(jù)赞别。 | 是 | 是 | 是 |
READ COMMITTED | 一個事務(wù)只能讀到另一個已經(jīng)提交的事務(wù)修改過的數(shù)據(jù)仿滔。 | 否 | 是 | 是 |
REPEATABLE READ | 一個事務(wù)只能讀到另一個已經(jīng)提交的事務(wù)修改過的數(shù)據(jù)崎页,而且該事務(wù)第一次讀過某條記錄后飒焦,即使其他事務(wù)修改了該記錄的值并且提交牺荠,該事務(wù)之后再讀該條記錄時休雌,讀到的仍是第一次讀到的值挑辆。 | 否 | 否 | 是 |
SERIALIZABLE | 事務(wù)串行化執(zhí)行 | 否 | 否 | 否 |
InnoDB默認(rèn)支持REPEATABLE READ,但與標(biāo)準(zhǔn)SQL不同箫荡,InnoDB在REPEATABLE READ事務(wù)隔離級別下羔挡,使用Next-Key Lock算法,避免了幻讀問題利术。
事務(wù)的實(shí)現(xiàn)
redo log
redo log稱為重做日志印叁,保證事務(wù)的原子性轮蜕,持久性
當(dāng)前事務(wù)數(shù)據(jù)庫系統(tǒng)普遍采用Write Ahred Log策略(WAL跃洛,預(yù)寫式日志)汇竭,即當(dāng)事務(wù)提交時韩玩,先寫重做日志找颓,再修改磁盤頁數(shù)據(jù)。如果宕機(jī)益老,則通過重做日志來完成數(shù)據(jù)的恢復(fù)捺萌。這也是事務(wù)ACID中D(Durability持久性)的要求酷誓。
實(shí)際上是最先操作是修改內(nèi)存池中的頁數(shù)據(jù)盐数。先寫重做日志是相對于修改磁盤數(shù)據(jù)而言玫氢。
任何對Innodb表的更新漾峡,Innodb都會將更新操作轉(zhuǎn)化為redo log并寫入磁盤,redo log中記錄了修改的詳細(xì)信息敬特。
redo log是物理日志,記錄頁的偏移量和字節(jié)等數(shù)據(jù)掰伸。
重做redo log合搅,實(shí)際是重做提交事務(wù)修改的頁物理操作歧蕉。
innodb_flush_log_at_trx_commit參數(shù)控制重做日志緩存刷新到磁盤的策略惯退。
1:默認(rèn)催跪,事務(wù)提交必須調(diào)用一次fsync操作
0:事務(wù)提交時不進(jìn)行寫入重做日志操作荣倾,由master thread每1秒進(jìn)行一次fsync操作
2:事務(wù)提交時僅將重做日志寫入文件系統(tǒng)緩存舌仍,不進(jìn)行fsync操作铸豁,當(dāng)mysql宕機(jī)而操作系統(tǒng)不宕機(jī)時推姻,不會導(dǎo)致事務(wù)丟失藏古。
除了事務(wù)提交時拧晕,刷新重做日志到磁盤還有如下場景
redo log buffer已經(jīng)使用一半內(nèi)存空間
Async/Sync Flush Checkpoint
log block
重做日志緩沖,重做日志文件都是以塊(block)的方法進(jìn)行保存的靡馁,稱為重做日志塊(redo log block)臭墨,每塊的大小為512字節(jié)。重做日志塊與磁盤扇區(qū)大小一樣侠畔,因此重做日志的寫入可以保證原子性红竭,不需要doublewrite技術(shù)德崭。
日志塊由三部分組成眉厨,依次為日志塊頭(log block header)憾股,日志內(nèi)容(log body)服球,日志塊尾log block tailer
日志塊頭內(nèi)容以下
變量 | 字節(jié) | 描述 |
---|---|---|
LOG_BLOCK_HDR_NO | 4 | log buffer由log block組成,在內(nèi)部log buffer就好似一個由log block數(shù)組粉渠,LOG_BLOCK_HDR_NO用于標(biāo)記這個數(shù)組的下標(biāo),遞增并且循環(huán)使用雕沉。第一位用做flush bit坡椒,最大值為2G |
LOG_BLOCK_HDR_DATA_LEN | 2 | 表示log block占用的大小倔叼。最大為0x200,表示log block512字節(jié) |
LOG_BLOCK_FIRST_REC_GROUP | 2 | 新事務(wù)第一個日志偏移量肥印。如果log block中存儲了事務(wù)L1后還有空余空間,還會存儲下一個事務(wù)L2的內(nèi)容敷硅,LOG_BLOCK_FIRST_REC_GROUP記錄事務(wù)L2的偏移量绞蹦。 |
若LOG_BLOCK_FIRST_REC_GROUP與LOG_BLOCK_HDR_DATA_LEN相同,表示當(dāng)前l(fā)og block不含新的日志
LOG_BLOCK_HDR_NO是通過lsn計(jì)算得到的榜旦,因此InnoDB也可以通過lsn定位到具體的redo log幽七。
日志塊尾只有LOG_BLOCK_TRL_NO,4字節(jié)溅呢,與LOG_BLOCK_CHECKPOINT_NO保持一致
log group
重做日志組是一個邏輯概念澡屡,由多個重做日志文件redo log file組成,每個log group中的日志大小是相同的咐旧。默認(rèn)重做日志組由ib_logfile0驶鹉,ib_logfile1組成铣墨。
InnoDB 1.2 版本前,重做日志組的總大小要小于4GB上忍,InnoDB 1.2 版本開始吓笙,提高到512GB
對log block的寫入追加在redo log file的最后部分叁鉴,當(dāng)一個redo log file被寫滿時常侣,會接著寫入下一個redo log file巾乳,使用方式為round-robin压状。
在架構(gòu)篇說過莺匠,當(dāng)頁被Checkpoint刷新到磁盤后遥缕,對應(yīng)的重做日志就不需要了 宝穗,其空間可以被覆蓋重用橱鹏。
每個redo group的第一個redo log file中前2kb不保存log block的信息杉辙,而是保存log file header,checkpoint信息。
redo log使用順序?qū)懀俣群芸欤玞heckpoint后叙量,需要更新第一個日志文件的頭部checkpoint標(biāo)記,這時并不是順序?qū)憽?br>
redo group非第一個redo log file中前2KB內(nèi)容并不存儲信息笆载,預(yù)留為空雄家。
redo log file前2k內(nèi)容勾效,依次存放以下數(shù)據(jù)
屬性 | 字節(jié) |
---|---|
LOG FILE HEADER | 512 |
CHECKPOINT BLOCK1 | 512 |
空 | 512 |
CHECKPOINT BLOCK2 | 512 |
重點(diǎn)看一下CHECKPOINT BLOCK的內(nèi)容
屬性 | 字節(jié) | 解析 |
---|---|---|
LOG_CHECKPOINT_NO | 8 | 單調(diào)遞增的值米死,每次checkpoint操作完成后進(jìn)行自增操作 |
LOG_CHECKPOINT_LSN | 8 | checkpoint的值 |
LOG_CHECKPOINT_OFFSET | 4 | checkpoint的值對應(yīng)的在重做日志的偏移量 |
LOG_CHECKPOINT_LSN表示checkpoint的值,LSN小于該值的頁都已經(jīng)被寫入到磁盤隧魄。CHECKPOINT BLOCK有兩個,InnoDB會交替進(jìn)行checkpoint值的更新汇跨。
這樣即使某次checkpoint block寫失敗了,那么崩潰恢復(fù)的時候從上一次記錄的checkpoint開始恢復(fù)朴译,也能正確的恢復(fù)數(shù)據(jù)庫事務(wù)迹辐。
恢復(fù)時嘿悬,InnoDB需要讀取這兩個CHECKPOINT BLOCK,取其中較大的LOG_CHECKPOINT_LSN轮纫,恢復(fù)大于該值的重做日志搓谆。
LSN
LSN 即日志序列號( Log Sequence Number ),它是每個redo log的序號瘤睹。
LSN存在多處渤涌,代表不同含義
1. 代表重做日志寫入總量
LSN是單調(diào)遞增的酌伊,保存在log_sys中(InnoDB運(yùn)行時會維護(hù)一個對象鼻由,負(fù)責(zé)管理 Redo Log Buffer蕉世,啟動時由log_init()函數(shù)負(fù)責(zé)初始化)
每寫入一個redo log時,LSN就會遞增該 Redo Log 寫入的字節(jié)數(shù)昭抒,例如新增一個log長度是len肠鲫,則log_sys->lsn += len。
2. 代表checkpoint最新位置氢哮,見上面的CHECKPOINT BLOCK中的LOG_CHECKPOINT_LSN袋毙。
當(dāng)InnoDB正常shutdown,在flush redo log和臟頁后冗尤,會做一次完全同步的checkpoint听盖,并將checkpoint的LSN寫到共享表空間的FSP HEADER PAGE的FIL_PAGE_FILE_FLUSH_LSN變量中。
(由于已經(jīng)完全checkpoint裂七,下次啟動時皆看,lsn可以被重新賦予常量初始值LOG_START_LSN)
Mysql啟動時,會讀取共享表空間中的FIL_PAGE_FILE_FLUSH_LSN背零,以及CHECKPOINT BLOCK中的較大的LOG_CHECKPOINT_LSN腰吟,如果兩者相同,則說明正常關(guān)閉徙瓶;否則毛雇,就需要進(jìn)行故障恢復(fù)录语。
通過LOG_CHECKPOINT_LSN找到對應(yīng)的redo log,掃描其后的redo log執(zhí)行恢復(fù)操作即可禾乘。
3. 代表頁最后刷新位置澎埠。每個頁的頭部都有一個FIL_PAGE_LSN,記錄該頁最后刷新時LSN的大小始藕,可用于判斷頁是否需要進(jìn)行恢復(fù)操作蒲稳。
參數(shù):innodb_fast_shutdown,控制數(shù)據(jù)庫關(guān)閉操作
0:關(guān)閉時伍派,需要完成所有full purge和merge insert buffer江耀,并將所有臟頁刷新到磁盤
1:默認(rèn)值,只是將臟頁刷新到磁盤
2:只保證日志都寫入到日志文件诉植,下次啟動祥国,會進(jìn)行恢復(fù)操作
參數(shù):innodb_force_recovery,控制數(shù)據(jù)庫恢復(fù)操作
默認(rèn)0晾腔,表示需要恢復(fù)時舌稀,進(jìn)行所有的恢復(fù)操作。
其他配置值不一一列出灼擂。
undo
undo log保證事務(wù)的原子性壁查, 幫助事務(wù)回滾以及MVCC功能。
undo是邏輯日志剔应,對每行數(shù)據(jù)進(jìn)行記錄睡腿,記錄的是每個操作的逆操作。
回滾操作峻贮,實(shí)際做的是先前相反的工作席怪,對于insert,做一個delete纤控,對于delete挂捻,做一個insert,對于update嚼黔,做一個相反的update细层。
undo的存儲
InnoDB將undo log看做數(shù)據(jù),通過Page保存undo log唬涧。
回滾段
回滾段也是一個段對象,保存在頁(0,6)處(共享表空間第6頁)盛撑,內(nèi)容如下
變量 | 字節(jié) | 描述 |
---|---|---|
TRX_RSEG_MAX_SIZE | 4 | 未使用 |
TRX_RSEG_HISTORY_SZIE | 4 | HISTORY鏈表中UNDO頁的數(shù)量 |
TRX_RSGE_HISTORY | 16 | 已提交的undo日志鏈表碎节,可被purge回收 |
TRX_RSEG_FSEG_HEADER | 10 | 回滾段的SEGMENT HEADER |
TRX_RSEG_UNDO_SLOTS | 4*1024 | 指向UNDO段SEGMENT HEADER所在頁的偏移量 |
一個UNDO段可以管理一個事務(wù),一個回滾段可以管理1024個UNDO段抵卫。
InnoDB1.1之前狮荔,只有一個回滾段胎撇,支持最大并發(fā)事務(wù)為1026。
InnoDB1.1開始殖氏,最大支持128個回滾段晚树。
位于(0,5)的FIL_PAGE_TYPE_SYS,記錄了所有回滾段所在頁雅采。
UNDO段
UNDO段是真正存儲undo log的地方爵憎。它實(shí)際上是一個UNDO頁鏈表。鏈表第一個UNDO頁由以下部分組成:
UNDO LOG PAGE HEADER
UNDO LOG SEGMENT HEADER
UNDO日志
UNDO LOG PAGE HEADER內(nèi)容如下
變量 | 字節(jié) | 描述 |
---|---|---|
TRX_UNDO_PAGE_TYPE | 2 | undo日志的類型婚瓜,TRX_UNDO_INSERT或TRX_UNDO_UPDATE |
TRX_UNOD_PAGE_STARE | 2 | UNDO頁最新一個事務(wù)undo日志所在位置 |
TRX_UNDO_PAGE_FREE | 2 | UNDO頁空閑的偏移量 |
TRX_UNDO_PAGE_NODE | 12 | UNDO頁的鏈表節(jié)點(diǎn) |
關(guān)于TRX_UNDO_PAGE_NODE宝鼓,可以參考存儲篇的鏈表結(jié)構(gòu)
UNDO LOG SEGMENT HEADER內(nèi)容如下
變量 | 字節(jié) | 描述 |
---|---|---|
TRX_UNDO_STATE | 2 | UNDO段的狀態(tài) |
TRX_UNDO_LAST_LOG | 2 | 最近一個undo log header在頁中的偏移量位置 |
TRX_UNDO_FSEG_HEADER | 10 | UNDO段的segment header |
TRX_UNDO_PAGE_LIST | 16 | UNDO頁的鏈表頭 |
TRX_UNDO_STATE的有效值有TRX_UNDO_ACTIVE,TRX_UNDO_CACHEd,TRX_UNDO_TO_FREE,TRX_UNDO_TO_PURGE。
UNDO LOG SEGMENT HEADER僅保存在UNDO頁鏈表的第一個UNDO頁中巴刻,其他UNDO頁中對應(yīng)位置保留為空
undo記錄結(jié)構(gòu)
每個undo記錄由兩部分組成
UNDO LOG HEADER
UNDO LOG RECORD
undo log record有update undo log record和insert undo log record兩種類型愚铡,通常insert操作產(chǎn)生insert undo log record,其他DML操作產(chǎn)生update undo log record胡陪。
通過TRX_UNDO_PAGE_TYPE可以看出沥寥,一個UNDO段只能存儲一種類型的undo,insert undo log或update undo log柠座。如果一個事務(wù)同時有INSERT营曼,UPDATE操作,則需要每種類型分配單獨(dú)的UNDO段愚隧,這樣也會導(dǎo)致InnoDB支持最大并發(fā)事務(wù)數(shù)下降蒂阱。
UNDO LOG HEADER內(nèi)容如下
變量 | 字節(jié) | 描述 |
---|---|---|
TRX_UNDO_TRX_ID | 8 | 產(chǎn)生undo日志的事務(wù)id |
TRX_UNDO_TRX_NO | 8 | 標(biāo)識事務(wù)提交順序的序號 |
TRX_UNDO_DEL_MARKS | 2 | 標(biāo)記本組 undo 日志中是否包含delete mark 產(chǎn)生的 undo 日志 |
TRX_UNDO_LOG_START | 2 | 表示本組 undo 日志中第一條 undo 日志的在頁面中的偏移量 |
TRX_UNDO_DICT_OPERATION | 2 | 是否為DDL操作 |
TRX_UNDO_TABLE_ID | 8 | 若是DDL操作,操作表的id |
TRX_UNDO_NEXT_LOG | 2 | 下一個UNDO LOG HEADER位置 |
TRX_UNDO_PREV_LOG | 2 | 上一個UNDO LOG HEADER位置 |
TRX_UNDO_HISTORY_NODE | 12 | HISTORY鏈表節(jié)點(diǎn) |
由于purge可能移除一些undo log record狂塘,TRX_UNDO_LOG_START不一定等于UNDO LOG HEADER結(jié)束位置偏移量录煤。
事務(wù)開啟時,會分配一個唯一的嚴(yán)格遞增的事務(wù)ID以及UNDO段荞胡,并設(shè)置其TRX_UNDO_STATE變量為TRX_UNDO_ACTIVE妈踊。
注意:
InnoDB將undo log看做數(shù)據(jù),UNDO頁與普通的數(shù)據(jù)頁一起管理泪漂,會依據(jù)LRU規(guī)則刷新出內(nèi)存廊营,后續(xù)再從磁盤讀取。
同樣萝勤,對undo log的操作也需要記錄到redo log中露筒。
如對于一個insert操作,redo log不僅要記錄insert操作敌卓,還需要記錄一個生成undo insert的操作慎式。
進(jìn)行恢復(fù)時,InnoDB會重做所有事務(wù),包括未提交的事務(wù)和回滾了的事務(wù)瘪吏。然后通過undo log回滾那些未提交的事務(wù)癣防。
參數(shù):
innodb_undo_directory:指定UNDO獨(dú)立表空間位置
innodb_undo_logs:設(shè)置rollback segment個數(shù),默認(rèn)為128(一個rollback segment支持1024并發(fā))掌眠,在InnoDB 1.2蕾盯,該參數(shù)替換之前版本的innodb_rollback_segments
innodb_undo_tablespaces:組成undo表空間文件個數(shù)
innodb_undo_log_truncate: MySQL 自動收縮 Undo 表空間,防止磁盤占用過大蓝丙,默認(rèn)開啟(Mysql5.7.5之后提供)
innodb_max_undo_log_size:超過該閥值將被自動收縮
UNDO頁復(fù)用
當(dāng)事務(wù)提交時级遭,需要處理UNDO頁:
如果當(dāng)前的undo log只占一個page,且占用的header page大小使用不足其3/4時(TRX_UNDO_PAGE_REUSE_LIMIT)迅腔,則狀態(tài)設(shè)置為TRX_UNDO_CACHED装畅,表示該UNDO頁可以復(fù)用,之后新的undo log記錄在當(dāng)前undo log的后面沧烈。
如果是Insert_undo(undo類型為TRX_UNDO_INSERT)掠兄,則狀態(tài)設(shè)置為TRX_UNDO_TO_FREE,該undo log可被刪除
如上不滿足锌雀,則表明該undo log可能需要Purge線程去執(zhí)行清理操作蚂夕,狀態(tài)設(shè)置為TRX_UNDO_TO_PURGE,將undo log加入到回滾段的TRX_RSGE_HISTORY中腋逆,由purge回收婿牍。
purge操作
purge用于最終完成delete和update操作,這樣設(shè)計(jì)是因?yàn)镮nnoDB支持MVCC惩歉,所以記錄不能在事務(wù)提交時立即進(jìn)行處理等脂,其他事務(wù)可能正在引用這行數(shù)據(jù)。
(delete操作將記錄的delete flag設(shè)置為1)
前面說過撑蚌,回滾段TRX_RSGE_HISTORY列表上遥,會根據(jù)事務(wù)提交的順序,將undo log鏈接起來争涌。
執(zhí)行purge過程中粉楚,InnoDB從TRX_RSGE_HISTORY列表中找到第一個需要被清理的記錄trx1,清理后InnoDB會在trx1所在undo log頁繼續(xù)查找是否存在可以被清理的記錄亮垫,直到該UNDO頁沒有可以清理的記錄模软,再回到history list中查找下一個需要被清理的記錄。
由于可以重用饮潦,一個undo log可能存放了不同事務(wù)的undo log燃异。因此purge操作需要涉及磁盤的離散讀取操作,是一個比較緩慢的過程害晦。
MVCC原理
隱藏列
在存儲篇說過特铝,行數(shù)據(jù)中有兩個隱藏列用于實(shí)現(xiàn)MVCC
TransactionID:DB_TRX_ID暑中,記錄操作該數(shù)據(jù)事務(wù)的事務(wù)ID
RollPointer:DB_ROLL_PTR壹瘟,指向上一個版本數(shù)據(jù)在undo log 里的位置指針
事務(wù)修改行數(shù)據(jù)時鲫剿,會將修改前的數(shù)據(jù)放入undo log中,并修改TransactionID為當(dāng)前事務(wù)ID稻轨,RollPointer指向上一個版本數(shù)據(jù)位置灵莲。
例如將行數(shù)據(jù)的一個字段從A -> B -> C,TransactionID殴俱,RollPointer變化如下
注意:這里通過RollPointer組織成一條undo log鏈政冻。
快照
在RR級別下,事務(wù)在begin/start transaction之后的第一條select讀操作后, 會創(chuàng)建一個快照(read view)线欲,將當(dāng)前系統(tǒng)中活躍的其他事務(wù)記錄記錄起來明场。
在RC級別下,事務(wù)中每條select語句都會創(chuàng)建一個快照李丰。
可見性判斷
設(shè)要讀取的行的最后提交事務(wù)id為 trx_id_current苦锨,
當(dāng)前事務(wù)創(chuàng)建的快照read view 中最早的事務(wù)id為up_limit_id, 最遲的事務(wù)id為low_limit_id
1. trx_id_current < up_limit_id, 當(dāng)前事務(wù)在讀取該行記錄時, 該行記錄的最新事務(wù)ID是小于當(dāng)前系統(tǒng)所有活躍的事務(wù),所以當(dāng)前行數(shù)據(jù)可見趴泌。
2. trx_id_current > low_limit_id, 當(dāng)前事務(wù)開啟后舟舒,該行記錄被修改并提交,數(shù)據(jù)不可見嗜憔。
3. up_limit_id <= trx_id_current <= low_limit_id秃励,該行記錄最新事務(wù)處于活動狀態(tài),
這時需要判斷 trx_id_current 在不在 快照的活躍事務(wù)ID列表中吉捶。
若不在夺鲜,數(shù)據(jù)可見。若在呐舔,不可見币励,需要查找undo log 鏈得到上一個版本再進(jìn)行可見性判斷。
group commit
對于InnoDB滋早,事務(wù)提交進(jìn)行兩個操作:
修改內(nèi)存中事務(wù)對應(yīng)的信息榄审,并將日志寫入重做日志緩沖
調(diào)用fsync將重做日志緩沖寫入磁盤
group commit,組提交杆麸,即將多個事務(wù)的重做日志緩沖通過一次fsync刷新到磁盤
開啟binlog后搁进,為了保證存儲引擎中的事務(wù)和binlog的一致性,InnoDB使用兩階段事務(wù)昔头。
注意:重做日志是innodb產(chǎn)生饼问,物理格式日志,記錄對每個頁的修改揭斧,在事務(wù)進(jìn)行中不斷寫入莱革。
而binlog是mysql上層產(chǎn)生的峻堰,是一種邏輯日志,在事務(wù)提交完成時一次寫入盅视。
兩階段事務(wù)步驟如下
Prepare 階段:SQL 已經(jīng)成功執(zhí)行并生成 redo 和 undo 的內(nèi)存日志捐名;InnoDB 將回滾段設(shè)置為 prepare 狀態(tài);
binlog提交階段:binlog 內(nèi)存日志數(shù)據(jù)寫入文件系統(tǒng)緩存并通過fsync() 寫入磁盤闹击;
Commit 階段:fsync() 將 binlog 文件系統(tǒng)緩存日志數(shù)據(jù)永久寫入磁盤镶蹋;
恢復(fù)操作
在 prepare 階段前崩潰,該事務(wù)直接回滾赏半;
在 binlog 已經(jīng) fsync() 贺归,但 InnoDB 未 commit 時崩潰;恢復(fù)時断箫,將會從 binlog 中獲取事務(wù)信息拂酣,重做該事務(wù)并提交,使 InnoDB 和 binlog 始終保持一致婶熬。
InnoDB需要保證 binlog 的寫入順序和 InnoDB 事務(wù)提交順序一致。
我們使用on-line backup下來的備份文件進(jìn)行恢復(fù)或者主備同步尸诽,因?yàn)镮nnoDB檢測最新的事務(wù)T3已經(jīng)Commit盯另,不需要進(jìn)行恢復(fù)性含,結(jié)果導(dǎo)致事務(wù)T1數(shù)據(jù)丟失。
InnoDB1.2版本前 鸳惯,使用 prepare_commit_mutex 保證順序商蕴,只有當(dāng)上一個事務(wù) commit 后釋放鎖,下個事務(wù)才可以進(jìn)行 prepare 操作芝发。但導(dǎo)致開啟二進(jìn)制日志后绪商,group commit功能失效,性能較差辅鲸。
InnoDB1.2版本后進(jìn)行了優(yōu)化格郁,
prepare 階段不變,
binlog提交階段和commit 階段拆分為三個過程独悴,每個階段都會去維護(hù)一個隊(duì)列例书,第一個進(jìn)入該隊(duì)列的作為 leader 線程,其他作為 follower 線程刻炒;leader 線程會收集 follower 的事務(wù)决采,并負(fù)責(zé)做 sync,follower 線程等待 leader 通知操作完成坟奥。
Flush階段树瞭,將隊(duì)列中每個事務(wù)的binlog都寫入內(nèi)存
Sync階段拇厢,將內(nèi)存隊(duì)列中的binlog刷新到磁盤,若隊(duì)列中有多個事務(wù)晒喷,僅一次fsync操作就完成日志的寫入孝偎。
Commit階段,leader根據(jù)隊(duì)列順序調(diào)用InnoDB事務(wù)的提交厨埋,這時就可以使用group commit功能邪媳。
由于三個階段都是根據(jù)隊(duì)列順序執(zhí)行操作捐顷,所以保證 binlog 的寫入順序和 InnoDB 事務(wù)提交順序一致荡陷。
當(dāng)有一組事物在進(jìn)行Commit階段時,其他新事物可以進(jìn)行Flush階段迅涮,從而使用group commit不斷生效废赞。