事務(wù)(Transaction)是數(shù)據(jù)庫區(qū)別于文件系統(tǒng)的重要特性之一界拦。事務(wù)會(huì)把數(shù)據(jù)庫從一種一致狀態(tài)轉(zhuǎn)換為另一種一致狀態(tài)。
事務(wù)可由一條非常簡單的 SQL 語句組成爪幻,也可以由一組復(fù)雜的 SQL 語句組成宗弯。事務(wù)是訪問并更新數(shù)據(jù)庫中各種數(shù)據(jù)項(xiàng)的一個(gè)程序執(zhí)行單元愤估。在事務(wù)中的操作,要么都做修改打洼,要么都不做龄糊。
事務(wù)的概念
InnoDB 存儲(chǔ)引擎中的事務(wù)完全符合 ACID 的特性:
原子性(Atomicity)
原子性指整個(gè)數(shù)據(jù)庫事務(wù)是不可分割的工作單位。只有使事務(wù)中所有的數(shù)據(jù)庫操作都執(zhí)行成功募疮,才算整個(gè)事務(wù)成功炫惩。事務(wù)中任何一個(gè) SQL 語句執(zhí)行失敗,已經(jīng)執(zhí)行成功的 SQL 語句也必須撤銷阿浓,數(shù)據(jù)庫狀態(tài)應(yīng)該退回到執(zhí)行事務(wù)前的狀態(tài)他嚷。一致性(Consistency)
一致性指事務(wù)將數(shù)據(jù)庫從一種狀態(tài)轉(zhuǎn)變?yōu)橄乱环N一致的狀態(tài)。在事務(wù)開始之前和事務(wù)結(jié)束之后芭毙,數(shù)據(jù)庫的完整性約束沒有被破壞筋蓖。隔離性(Isolation)
隔離性又叫做并發(fā)控制(concurrency control)
、可串行化(serializability)
退敦、鎖(locking)
等粘咖。事務(wù)的隔離性要求每個(gè)讀寫事務(wù)的對(duì)象對(duì)其他事務(wù)的操作對(duì)象能相互分離,即該事務(wù)提交前對(duì)其他事務(wù)都不可見侈百,通常這使用鎖來實(shí)現(xiàn)瓮下。由定義可見,一般用于出發(fā)數(shù)據(jù)的的并發(fā)請(qǐng)求钝域。持久性(durability)
事務(wù)一旦提交讽坏,其結(jié)果就是永久性的。即使發(fā)生宕機(jī)等故障网梢,數(shù)據(jù)庫也能將數(shù)據(jù)恢復(fù)震缭。但是只能從事務(wù)本身的角度來保證結(jié)果的永久性,一些外部原因的故障導(dǎo)致的數(shù)據(jù)丟失是無法保障的战虏。因此持久性保證事務(wù)系統(tǒng)的高可靠性(High Reliability)
拣宰,而不是高可用性(High Availability)
事務(wù)的實(shí)現(xiàn)
事務(wù)的隔離性由鎖來實(shí)現(xiàn)党涕,關(guān)于 InnoDB 存儲(chǔ)引擎中的鎖,可以參考本人之前的一篇文章巡社。
原子性膛堤、一致性、持久性通過數(shù)據(jù)庫的 redo log 和 undo log 來完成晌该。redo log 稱為重做日志肥荔,用來保證事務(wù)的原子性和持久性。undo log 用來保證事務(wù)的一致性朝群。
redo
redo log 用來實(shí)現(xiàn)事務(wù)的持久性燕耿,記錄的是對(duì)于每個(gè)頁的修改。
其由兩部分組成:
- 內(nèi)存中的重做日志緩沖(redo log buffer)姜胖,因?yàn)槭谴嬖谟趦?nèi)存中誉帅,其是易失的;
- 重做日志文件(redo log file)右莱,其是持久的
InnoDB 存儲(chǔ)引擎通過 Force Log at Commit
機(jī)制來實(shí)現(xiàn)事務(wù)的持久性蚜锨,即當(dāng)事務(wù)提交(COMMIT)使,必須先講該事務(wù)的所有日志寫入到重做日志文件進(jìn)行持久化慢蜓,待事務(wù)的 COMMIT 操作完成才算完成亚再。為了確保每次日志都寫入重做日志文件,在每次將重做日志緩沖寫入重做日志文件后晨抡,InnoDB 存儲(chǔ)引擎都需要調(diào)用一次 fsync 操作(確保重做日志從文件系統(tǒng)緩存寫入到磁盤)氛悬。
與 binlog 的區(qū)別
接觸過 MySQL 的同學(xué)應(yīng)該多少都會(huì)了解 binlog
,會(huì)發(fā)現(xiàn) binlog 與 redo log 非常相似凄诞,都是記錄了對(duì)于數(shù)據(jù)操作的日志圆雁,為什么還要引入 redo log?binlog的兩個(gè)重要使用場景:
- MySQL 主從復(fù)制
- 數(shù)據(jù)恢復(fù)
但是兩者是存在非常大的區(qū)別的:
- redo log 是在 InnoDB 存儲(chǔ)引擎層產(chǎn)生帆谍,binlog 是在 MySQL 數(shù)據(jù)庫的上層產(chǎn)生伪朽,不僅僅針對(duì) InnoDB 存儲(chǔ)引擎
- 內(nèi)容形式不同。binlog 是一種邏輯日志汛蝙,記錄的是對(duì)應(yīng)的 SQL 語句或行的內(nèi)容烈涮;redo log 是物理格式日志,記錄的是對(duì)于每個(gè)頁(InnoDB 數(shù)據(jù)的存儲(chǔ)格式)的修改
- 寫入磁盤的時(shí)間點(diǎn)不同窖剑。binlog 只在事務(wù)提交完成后進(jìn)行一次寫入坚洽;redo log 在事務(wù)進(jìn)行中不斷地被寫入。
- redo log 空間是固定的西土,可重用讶舰;binlog 是追加寫,當(dāng)前文件寫完之后會(huì)開啟一個(gè)新文件繼續(xù)寫。
InnoDB 存儲(chǔ)引擎在啟動(dòng)時(shí)不管上次數(shù)據(jù)庫運(yùn)行時(shí)是否正常關(guān)閉跳昼,都會(huì)嘗試進(jìn)行恢復(fù)操作般甲。redo log 記錄的是物理日志,恢復(fù)速度比 binlog 要快很多鹅颊,而且InnoDB 恢復(fù)還進(jìn)行了一定程度的優(yōu)化-順序讀取及并行應(yīng)用redo log敷存。
group commit
事務(wù)的兩階段提交:
- 修改內(nèi)存中事務(wù)對(duì)應(yīng)的信息,并且將日志寫入重做日志緩沖
- 調(diào)用 fsync 確保日志都從重做日志緩沖寫入磁盤
若事務(wù)為非只讀事務(wù)堪伍,則每次事務(wù)提交時(shí)需要進(jìn)行一次 fsync 操作锚烦,以此保證重做日志已經(jīng)寫入磁盤。
因?yàn)榇疟P的 fsync 性能時(shí)有限的帝雇,即上面的第二階段是個(gè)較慢的過程涮俄。但是當(dāng)有事務(wù)進(jìn)行第二階段提交時(shí),其他事務(wù)可以進(jìn)行第一階段的提交尸闸,正在提交的事務(wù)完成提交操作后禽拔,再次進(jìn)行第二階段,即 group commit 室叉,一次 fsync 可以刷新確保多個(gè)事務(wù)日志被寫入文件。
但是開啟 binlog 之后硫惕,group commit 功能就會(huì)失效茧痕。這是因?yàn)殚_啟 binlog 之后,需要保證binlog 和 redo log 的一致性恼除,二者之間使用了兩階段事務(wù)踪旷,步驟如下:
- 事務(wù)提交時(shí) InnoDB 存儲(chǔ)引擎進(jìn)行 prepare 操作
- MySQL 數(shù)據(jù)庫上層寫入 binlog
- InnoDB 存儲(chǔ)引擎將日志寫入 redo log
- 寫入redo log 緩沖
- 調(diào)用 fsync
開啟 binlog 后 InnoDB 存儲(chǔ)引擎的提交過程.png
為了保證 binlog 的寫入順序和 事務(wù)提交順序一致,MySQL 內(nèi)部使用了prepare_commit_mutex
鎖豁辉,啟用該鎖后,步驟3.1 不可以在其他事務(wù)執(zhí)行 3.2 時(shí)進(jìn)行徽级,從而導(dǎo)致了 group commit 失效气破。
MySQL 5.6 采用 Binary Log Group Commit(BLGC) 來提高日志的寫入性能:
在 MySQL 數(shù)據(jù)庫上層進(jìn)行提交時(shí)首先按順序?qū)⑵浞湃胍粋€(gè)隊(duì)列中现使,隊(duì)列中的第一個(gè)事務(wù)稱為 leader,其他事務(wù)稱為 follower碳锈,leader 控制著 follower 的行為。
- Flush 階段欺抗,將每個(gè)事務(wù)的 binlog 寫入內(nèi)存中
- Sync 階段售碳,內(nèi)存中的 binlog 刷新到磁盤(如果隊(duì)列中有多個(gè)事務(wù),一次 fsync 操作就完成了多個(gè) binlog 的寫入)
- Commit 階段,leader 根據(jù)順序調(diào)用存儲(chǔ)引擎層事務(wù)的提交贸人,InnoDB 可以繼續(xù)使用 group commit
undo
undo log 用于將數(shù)據(jù)回滾到修改之前的樣子间景,因?yàn)榛貪L做的事情實(shí)際上是與之前相反的工作。所以灸姊,對(duì)于 INSERT 拱燃,undo log 會(huì)對(duì)應(yīng)一個(gè) DELETE 記錄;對(duì)于 DELETE力惯,畫丶do log 對(duì)應(yīng)一個(gè) INSERT碗誉;對(duì)于 UPDATE,undo log 對(duì)應(yīng)一個(gè)相反的 UPDATE父晶。
undo log 另一個(gè)作用是 MVCC哮缺,當(dāng)用戶讀取一行記錄時(shí),若該記錄已經(jīng)被其他事務(wù)占用甲喝,當(dāng)前事務(wù)可以通過 undo 讀取之前的行版本信息尝苇,從而實(shí)現(xiàn) 一致性非鎖定讀。
undo 存儲(chǔ)管理
InnoDB 存儲(chǔ)引擎對(duì) undo 的管理采用段(segment)
的方式埠胖,rollback segment 稱為回滾段糠溜,每個(gè)回滾段記錄了 1024 個(gè) undo log segment。
在 InnoDB 存儲(chǔ)引擎中直撤,undo log 分為 insert undo log
非竿、update undo log
。
- insert undo log 是指在 insert 操作中產(chǎn)生的 undo log谋竖,因?yàn)?insert 操作只對(duì)本事務(wù)自身可見红柱,故該 undo log 可以在事務(wù)提交后直接刪除。
- update undo log 記錄的是對(duì) delete 和 update 操作產(chǎn)生的 undo log蓖乘。該 undo log 可能需要提供 MVCC 機(jī)制锤悄,因此不能在事務(wù)提交時(shí)就進(jìn)行刪除,提交時(shí)放入 undo log 鏈表嘉抒,等待 purge 線程進(jìn)行最后的刪除零聚。
事務(wù)提交后并不能馬上刪除 undo log 及 undo log 所在的頁。因?yàn)榭赡苓€有其他事務(wù)需要通過 undo log 來得到行記錄之前的版本些侍。事務(wù)提交時(shí)將 undo log 放入一個(gè)鏈表中握牧,是否可以最終刪除 undo log 及 undo log 所在頁由 purge 線程來判斷。并且提交事務(wù)時(shí)娩梨,還會(huì)判斷 undo log 頁是否可以重用沿腰,如果可以重用,則會(huì)分配給后面的事務(wù)狈定。
purge
purge 操作用于最終完成 delete
和 update
操作颂龙。
InnoDB 存儲(chǔ)引擎有一個(gè) history 列表习蓬,根據(jù)事務(wù)提交的順序,將 undo log 進(jìn)行鏈接躲叼。InnoDB 存儲(chǔ)引擎先從 history list 中找 undo log企巢,然后再從 undo page 中找 undo log浪规,避免大量的隨機(jī)讀取操作,從而提高 purge 的效率誉裆。
redo log 用來保證事務(wù)的持久性足丢,undo log 用來幫助事務(wù)回滾以及 MVCC 的功能斩跌。redo log 基本上都是順序?qū)懙睦袒牛跀?shù)據(jù)運(yùn)行時(shí)不需要對(duì) redo log 的文件進(jìn)行讀取操作卿闹,而 undo log 是需要進(jìn)行隨機(jī)讀取的锻霎。