本篇內(nèi)容介紹了“MySQL事務(wù)工作流程原理是什么”的有關(guān)知識(shí),在實(shí)際案例的操作過(guò)程中,不少人都會(huì)遇到這樣的困境,接下來(lái)就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
成都創(chuàng)新互聯(lián)公司主要從事網(wǎng)站設(shè)計(jì)制作、成都網(wǎng)站建設(shè)、網(wǎng)頁(yè)設(shè)計(jì)、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)北京,十多年網(wǎng)站建設(shè)經(jīng)驗(yàn),價(jià)格優(yōu)惠、服務(wù)專(zhuān)業(yè),歡迎來(lái)電咨詢(xún)建站服務(wù):18980820575
事務(wù)的原子性是通過(guò) undo log 來(lái)實(shí)現(xiàn)的
事務(wù)的持久性是通過(guò) redo log 來(lái)實(shí)現(xiàn)的
事務(wù)的隔離性是通過(guò) (讀寫(xiě)鎖+MVCC)來(lái)實(shí)現(xiàn)的
而事務(wù)的終極大 boss 一致性是通過(guò)原子性,持久性,隔離性來(lái)實(shí)現(xiàn)的?。?!
問(wèn)題1: 為什么需要redo log?
InnoDB作為MySQL的存儲(chǔ)引擎,數(shù)據(jù)是存放在磁盤(pán)中的,但如果每次讀寫(xiě)數(shù)據(jù)都需要磁盤(pán)IO,效率會(huì)很低。為此,InnoDB提供了緩存(Buffer Pool),作為訪問(wèn)數(shù)據(jù)庫(kù)的緩沖:當(dāng)從數(shù)據(jù)庫(kù)讀取數(shù)據(jù)時(shí),會(huì)首先從Buffer Pool中讀取,如果Buffer Pool中沒(méi)有,則從磁盤(pán)讀取后放入Buffer Pool;當(dāng)向數(shù)據(jù)庫(kù)寫(xiě)入數(shù)據(jù)時(shí),會(huì)首先寫(xiě)入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會(huì)定期刷新到磁盤(pán)中 。
Buffer Pool的使用大大提高了讀寫(xiě)數(shù)據(jù)的效率,但是也帶了新的問(wèn)題:如果MySQL宕機(jī),而此時(shí)Buffer Pool中修改的數(shù)據(jù)還沒(méi)有刷新到磁盤(pán),就會(huì)導(dǎo)致數(shù)據(jù)的丟失,事務(wù)的持久性無(wú)法保證。
問(wèn)題2:redo log如何保證事務(wù)的持久性?
Redo log可以簡(jiǎn)單分為以下兩個(gè)部分:
一是內(nèi)存中重做日志緩沖 (redo log buffer),是易失的,在內(nèi)存中
二是重做日志文件 (redo log file),是持久的,保存在磁盤(pán)中
這里再細(xì)說(shuō)下寫(xiě)入Redo的時(shí)機(jī):
在數(shù)據(jù)頁(yè)修改完成之后,在臟頁(yè)刷出磁盤(pán)之前,寫(xiě)入redo日志。注意的是先修改數(shù)據(jù),后寫(xiě)日志
redo日志比數(shù)據(jù)頁(yè)先寫(xiě)回磁盤(pán)
聚集索引、二級(jí)索引、undo頁(yè)面的修改,均需要記錄Redo日志
在 MySQL中,如果每一次的更新操作都需要寫(xiě)進(jìn)磁盤(pán),然后磁盤(pán)也要找到對(duì)應(yīng)的那條記錄,然后再更新,整個(gè)過(guò)程 IO 成本、查找成本都很高。為了解決這個(gè)問(wèn)題,MySQL 的設(shè)計(jì)者就采用了日志(redo log)來(lái)提升更新效率。
當(dāng)事務(wù)提交時(shí),先將 redo log buffer 寫(xiě)入到 redo log file 進(jìn)行持久化,待事務(wù)的commit操作完成時(shí)才算完成。這種做法也被稱(chēng)為 Write-Ahead Log(預(yù)先日志持久化),在持久化一個(gè)數(shù)據(jù)頁(yè)之前,先將內(nèi)存中相應(yīng)的日志頁(yè)持久化。
具體來(lái)說(shuō),當(dāng)有一條記錄需要更新的時(shí)候,InnoDB 引擎就會(huì)先把記錄寫(xiě)到 redo log(redo log buffer)里面,并更新內(nèi)存(buffer pool),這個(gè)時(shí)候更新就算完成了。同時(shí),InnoDB 引擎會(huì)在適當(dāng)?shù)臅r(shí)候(如系統(tǒng)空閑時(shí)),將這個(gè)操作記錄更新到磁盤(pán)里面(刷臟頁(yè))。
在一個(gè)事務(wù)中可以修改多個(gè)頁(yè),Write-Ahead Log 可以保證單個(gè)數(shù)據(jù)頁(yè)的一致性,但是無(wú)法保證事務(wù)的持久性,F(xiàn)orce-log-at-commit 要求當(dāng)一個(gè)事務(wù)提交時(shí),其產(chǎn)生所有的mini-transaction 日志必須刷新到磁盤(pán)中,若日志刷新完成后,在緩沖池中的頁(yè)刷新到持久化存儲(chǔ)設(shè)備前數(shù)據(jù)庫(kù)發(fā)生了宕機(jī),那么數(shù)據(jù)庫(kù)重啟時(shí),可以通過(guò)日志來(lái)保證數(shù)據(jù)的完整性。
問(wèn)題3:重寫(xiě)日志的流程?
上圖表示了重做日志的寫(xiě)入流程,每個(gè)mini-transaction對(duì)應(yīng)每一條DML操作,比如一條update語(yǔ)句,其由一個(gè)mini-transaction來(lái)保證,對(duì)數(shù)據(jù)修改后,產(chǎn)生redo1,首先將其寫(xiě)入mini-transaction私有的Buffer中,update語(yǔ)句結(jié)束后,將redo1從私有Buffer拷貝到公有的Log Buffer中。當(dāng)整個(gè)外部事務(wù)提交時(shí),將redo log buffer再刷入到redo log file中。( redo log是按照順序?qū)懭氲?,磁盤(pán)的順序讀寫(xiě)的速度遠(yuǎn)大于隨機(jī)讀寫(xiě))
問(wèn)題4:數(shù)據(jù)寫(xiě)入后的最終落盤(pán),是從 redo log 更新過(guò)來(lái)的還是從 buffer pool 更新過(guò)來(lái)的呢?
實(shí)際上,redo log 并沒(méi)有記錄數(shù)據(jù)頁(yè)的完整數(shù)據(jù),所以它并沒(méi)有能力自己去更新磁盤(pán)數(shù)據(jù)頁(yè),也就不存在由 redo log 更新過(guò)去數(shù)據(jù)最終落盤(pán)的情況。
① 數(shù)據(jù)頁(yè)被修改以后,跟磁盤(pán)的數(shù)據(jù)頁(yè)不一致,稱(chēng)為臟頁(yè)。最終數(shù)據(jù)落盤(pán),就是把內(nèi)存中的數(shù)據(jù)頁(yè)寫(xiě)盤(pán)。這個(gè)過(guò)程與 redo log 毫無(wú)關(guān)系。
② 在崩潰恢復(fù)場(chǎng)景中,InnoDB 如果判斷到一個(gè)數(shù)據(jù)頁(yè)可能在崩潰恢復(fù)的時(shí)候丟失了更新,就會(huì)將它讀到內(nèi)存,然后讓 redo log 更新內(nèi)存內(nèi)容。更新完成后,內(nèi)存頁(yè)變成臟頁(yè),就回到了第一種情況的狀態(tài)
問(wèn)題5:redo log buffer 是什么?是先修改內(nèi)存,還是先寫(xiě) redo log 文件?
在一個(gè)事務(wù)的更新過(guò)程中,日志是要寫(xiě)多次的。比如下面這個(gè)事務(wù):
Copybegin;
INSERT INTO T1 VALUES ('1', '1');
INSERT INTO T2 VALUES ('1', '1');
commit;
這個(gè)事務(wù)要往兩個(gè)表中插入記錄,插入數(shù)據(jù)的過(guò)程中,生成的日志都得先保存起來(lái),但又不能在還沒(méi) commit 的時(shí)候就直接寫(xiě)到 redo log 文件里。
因此就需要 redo log buffer 出場(chǎng)了,它就是一塊內(nèi)存,用來(lái)先存 redo 日志的。也就是說(shuō),在執(zhí)行第一個(gè) insert 的時(shí)候,數(shù)據(jù)的內(nèi)存被修改了,redo log buffer 也寫(xiě)入了日志。
但是,真正把日志寫(xiě)到 redo log 文件,是在執(zhí)行 commit 語(yǔ)句的時(shí)候做的。
redo log buffer 本質(zhì)上只是一個(gè) byte 數(shù)組,但是為了維護(hù)這個(gè) buffer 還需要設(shè)置很多其他的 meta data,這些 meta data 全部封裝在 log_t 結(jié)構(gòu)體中。
問(wèn)題6:redo log順序?qū)懭氪疟P(pán)?
redo log以順序的方式寫(xiě)入文件,當(dāng)全部文件寫(xiě)滿(mǎn)的時(shí)候則回到第一個(gè)文件相應(yīng)的起始位置進(jìn)行覆蓋寫(xiě),每次提交事務(wù)之后,都先將相關(guān)的操作日志寫(xiě)入redo日志文件中,并且都追加到文件末尾,這是一個(gè)順序I/O
圖中展示了一組 4 個(gè)文件的 redo log 日志,checkpoint 是當(dāng)前要擦除的位置,擦除記錄前需要先把對(duì)應(yīng)的數(shù)據(jù)落盤(pán)(更新內(nèi)存頁(yè),等待刷臟頁(yè))。write pos 到 checkpoint 之間的部分可以用來(lái)記錄新的操作,如果 write pos 和 checkpoint 相遇,說(shuō)明 redolog 已滿(mǎn),這個(gè)時(shí)候數(shù)據(jù)庫(kù)停止進(jìn)行數(shù)據(jù)庫(kù)更新語(yǔ)句的執(zhí)行,轉(zhuǎn)而進(jìn)行 redo log 日志同步到磁盤(pán)中。checkpoint 到 write pos 之間的部分等待落盤(pán)(先更新內(nèi)存頁(yè),然后等待刷臟頁(yè))。
有了 redo log 日志,那么在數(shù)據(jù)庫(kù)進(jìn)行異常重啟的時(shí)候,可以根據(jù) redo log 日志進(jìn)行恢復(fù),也就達(dá)到了 crash-safe。
redo log 用于保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個(gè)參數(shù)設(shè)置成 1 的時(shí)候,表示每次事務(wù)的 redo log 都直接持久化到磁盤(pán)。這個(gè)參數(shù)建議設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后數(shù)據(jù)不丟失
MySQL 整體來(lái)看,其實(shí)就有兩塊:一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;還有一塊是引擎層,負(fù)責(zé)存儲(chǔ)相關(guān)的具體事宜。上面我們聊到的 redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱(chēng)為 binlog(歸檔日志)
為什么會(huì)有兩份日志呢?
因?yàn)樽铋_(kāi)始 MySQL 里并沒(méi)有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒(méi)有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個(gè)公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒(méi)有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來(lái)實(shí)現(xiàn) crash-safe 能力。
這兩種日志有以下三點(diǎn)不同。
① redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實(shí)現(xiàn)的,所有引擎都可以使用。
② redo log 是物理日志,記錄的是“在某個(gè)數(shù)據(jù)頁(yè)上做了什么修改”;binlog 是邏輯日志,記錄的是這個(gè)語(yǔ)句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。
③ redo log 是循環(huán)寫(xiě)的,空間固定會(huì)用完;binlog 是可以追加寫(xiě)入的?!白芳訉?xiě)”是指 binlog 文件寫(xiě)到一定大小后會(huì)切換到下一個(gè),并不會(huì)覆蓋以前的日志。
有了對(duì)這兩個(gè)日志的概念性理解后,再來(lái)看執(zhí)行器和 InnoDB 引擎在執(zhí)行這個(gè) update 語(yǔ)句時(shí)的內(nèi)部流程。
① 執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹(shù)搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁(yè)本來(lái)就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤(pán)讀入內(nèi)存,然后再返回。
② 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個(gè)值加上 1,比如原來(lái)是 N,現(xiàn)在就是 N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫(xiě)入這行新數(shù)據(jù)。
③ 引擎將這行新數(shù)據(jù)更新到內(nèi)存(InnoDB Buffer Pool)中,同時(shí)將這個(gè)更新操作記錄到 redo log 里面,此時(shí) redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時(shí)可以提交事務(wù)。
④ 執(zhí)行器生成這個(gè)操作的 binlog,并把 binlog 寫(xiě)入磁盤(pán)。
⑤ 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫(xiě)入的 redo log 改成提交(commit)狀態(tài),更新完成
其中將 redo log 的寫(xiě)入拆成了兩個(gè)步驟:prepare 和 commit,這就是兩階段提交(2PC)
問(wèn)題1: 兩階段提交原理?
MySQL 使用兩階段提交主要解決 binlog 和 redo log 的數(shù)據(jù)一致性的問(wèn)題。
兩階段提交原理描述:
① redo log 寫(xiě)盤(pán),InnoDB 事務(wù)進(jìn)入 prepare 狀態(tài)。
② 如果前面 prepare 成功,binlog 寫(xiě)盤(pán),那么再繼續(xù)將事務(wù)日志持久化到 binlog,如果持久化成功,那么 InnoDB 事務(wù)則進(jìn)入 commit 狀態(tài) 。
redo log 和 binlog 有一個(gè)共同的數(shù)據(jù)字段,叫 XID。崩潰恢復(fù)的時(shí)候,會(huì)按順序掃描 redo log:
① 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;
② 如果碰到只有 parepare、而沒(méi)有 commit 的 redo log,就拿著 XID 去 binlog 找對(duì)應(yīng)的事務(wù)。
binlog無(wú)記錄,回滾事務(wù)
binlog有記錄,提交事務(wù)
問(wèn)題2:為什么必須有“兩階段提交”呢?
如果不使用兩階段提交,假設(shè)當(dāng)前 ID=2 的行,字段 c 的值是 0,再假設(shè)執(zhí)行 update 語(yǔ)句過(guò)程中在寫(xiě)完第一個(gè)日志后,第二個(gè)日志還沒(méi)有寫(xiě)完期間發(fā)生了 crash,會(huì)出現(xiàn)什么情況呢?
**先寫(xiě) redo log 后寫(xiě) binlog。**假設(shè)在 redo log 寫(xiě)完,binlog 還沒(méi)有寫(xiě)完的時(shí)候,MySQL 進(jìn)程異常重啟。由于我們前面說(shuō)過(guò)的,redo log 寫(xiě)完之后,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來(lái),所以恢復(fù)后這一行 c 的值是 1。
但是由于 binlog 沒(méi)寫(xiě)完就 crash 了,這時(shí)候 binlog 里面就沒(méi)有記錄這個(gè)語(yǔ)句。因此,之后備份日志的時(shí)候,存起來(lái)的 binlog 里面就沒(méi)有這條語(yǔ)句。
然后你會(huì)發(fā)現(xiàn),如果需要用這個(gè) binlog 來(lái)恢復(fù)臨時(shí)庫(kù)的話(huà),由于這個(gè)語(yǔ)句的 binlog 丟失,這個(gè)臨時(shí)庫(kù)就會(huì)少了這一次更新,恢復(fù)出來(lái)的這一行 c 的值就是 0,與原庫(kù)的值不同。
**先寫(xiě) binlog 后寫(xiě) redo log。**如果在 binlog 寫(xiě)完之后 crash,由于 redo log 還沒(méi)寫(xiě),崩潰恢復(fù)以后這個(gè)事務(wù)無(wú)效,所以這一行 c 的值是 0。但是 binlog 里面已經(jīng)記錄了“把 c 從 0 改成 1”這個(gè)日志。所以,在之后用 binlog 來(lái)恢復(fù)的時(shí)候就多了一個(gè)事務(wù)出來(lái),恢復(fù)出來(lái)的這一行 c 的值就是 1,與原庫(kù)的值不同。
可以看到,如果不使用“兩階段提交”,那么數(shù)據(jù)庫(kù)的狀態(tài)就有可能和用它的日志恢復(fù)出來(lái)的庫(kù)的狀態(tài)不一致。
簡(jiǎn)單說(shuō),redo log 和 binlog 都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個(gè)狀態(tài)保持邏輯上的一致。
undo log有兩個(gè)作用:提供回滾和多版本控制(MVCC)
在數(shù)據(jù)修改的時(shí)候,不僅記錄了redo,還記錄了相對(duì)應(yīng)的undo,undo log主要記錄的是數(shù)據(jù)的邏輯變化,為了在發(fā)生錯(cuò)誤時(shí)回滾之前的操作,需要將之前的操作都記錄下來(lái),然后在發(fā)生錯(cuò)誤時(shí)才可以回滾。
undo日志,只將數(shù)據(jù)庫(kù)邏輯地恢復(fù)到原來(lái)的樣子,在回滾的時(shí)候,它實(shí)際上是做的相反的工作,比如一條INSERT ,對(duì)應(yīng)一條 DELETE,對(duì)于每個(gè)UPDATE,對(duì)應(yīng)一條相反的 UPDATE,將修改前的行放回去。undo日志用于事務(wù)的回滾操作進(jìn)而保障了事務(wù)的原子性。
實(shí)現(xiàn)原子性的關(guān)鍵,是當(dāng)事務(wù)回滾時(shí)能夠撤銷(xiāo)所有已經(jīng)成功執(zhí)行的sql語(yǔ)句。 InnoDB 實(shí)現(xiàn)回滾,靠的是undo log :當(dāng)事務(wù)對(duì)數(shù)據(jù)庫(kù)進(jìn)行修改時(shí),InnoDB 會(huì)生成對(duì)應(yīng)的undo log 如果事務(wù)執(zhí)行失敗或調(diào)用了rollback,導(dǎo)致事務(wù)需要回滾,便可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子。
在InnoDB存儲(chǔ)引擎中,undo log分為:
insert undo log
update undo log
insert undo log是指在insert 操作中產(chǎn)生的undo log,因?yàn)閕nsert操作的記錄,只對(duì)事務(wù)本身可見(jiàn),對(duì)其他事務(wù)不可見(jiàn)。故該undo log可以在事務(wù)提交后直接刪除,不需要進(jìn)行purge操作。
而update undo log記錄的是對(duì)delete 和update操作產(chǎn)生的undo log,該undo log可能需要提供MVCC機(jī)制,因此不能再事務(wù)提交時(shí)就進(jìn)行刪除。提交時(shí)放入undo log鏈表,等待purge線程進(jìn)行最后的刪除。
補(bǔ)充:purge線程兩個(gè)主要作用是:清理undo頁(yè)和清除page里面帶有Delete_Bit標(biāo)識(shí)的數(shù)據(jù)行。在InnoDB中,事務(wù)中的Delete操作實(shí)際上并不是真正的刪除掉數(shù)據(jù)行,而是一種Delete Mark操作,在記錄上標(biāo)識(shí)Delete_Bit,而不刪除記錄。是一種"假刪除",只是做了個(gè)標(biāo)記,真正的刪除工作需要后臺(tái)purge線程去完成。
innodb中通過(guò)B+樹(shù)作為索引的數(shù)據(jù)結(jié)構(gòu),并且主鍵所在的索引為ClusterIndex(聚簇索引), ClusterIndex中的葉子節(jié)點(diǎn)中保存了對(duì)應(yīng)的數(shù)據(jù)內(nèi)容。一個(gè)表只能有一個(gè)主鍵,所以只能有一個(gè)聚簇索引,如果表沒(méi)有定義主鍵,則選擇第一個(gè)非NULL唯一索引作為聚簇索引,如果還沒(méi)有則生成一個(gè)隱藏id列作為聚簇索引。
除了Cluster Index外的索引是Secondary Index(輔助索引)。輔助索引中的葉子節(jié)點(diǎn)保存的是聚簇索引的葉子節(jié)點(diǎn)的值。
InnoDB行記錄中除了剛才提到的rowid外,還有trx_id和db_roll_ptr, trx_id表示最近修改的事務(wù)的id,db_roll_ptr指向undo segment中的undo log。
新增一個(gè)事務(wù)時(shí)事務(wù)id會(huì)增加,trx_id能夠表示事務(wù)開(kāi)始的先后順序。
Undo log分為Insert和Update兩種,delete可以看做是一種特殊的update,即在記錄上修改刪除標(biāo)記。
update undo log記錄了數(shù)據(jù)之前的數(shù)據(jù)信息,通過(guò)這些信息可以還原到之前版本的狀態(tài)。
當(dāng)進(jìn)行插入操作時(shí),生成的Insert undo log在事務(wù)提交后即可刪除,因?yàn)槠渌聞?wù)不需要這個(gè)undo log。
進(jìn)行刪除修改操作時(shí),會(huì)生成對(duì)應(yīng)的undo log,并將當(dāng)前數(shù)據(jù)記錄中的db_roll_ptr指向新的undo log
MVCC (MultiVersion Concurrency Control) 叫做多版本并發(fā)控制。
InnoDB的 MVCC ,是通過(guò)在每行記錄的后面保存兩個(gè)隱藏的列來(lái)實(shí)現(xiàn)的。這兩個(gè)列, 一個(gè)保存了行的創(chuàng)建時(shí)間,一個(gè)保存了行的過(guò)期時(shí)間, 當(dāng)然存儲(chǔ)的并不是實(shí)際的時(shí)間值,而是系統(tǒng)版本號(hào)。
主要實(shí)現(xiàn)思想是通過(guò)數(shù)據(jù)多版本來(lái)做到讀寫(xiě)分離。從而實(shí)現(xiàn)不加鎖讀進(jìn)而做到讀寫(xiě)并行。
MVCC在mysql中的實(shí)現(xiàn)依賴(lài)的是undo log與read view
undo log :undo log 中記錄某行數(shù)據(jù)的多個(gè)版本的數(shù)據(jù)。
read view :用來(lái)判斷當(dāng)前版本數(shù)據(jù)的可見(jiàn)性
InnoDB 在實(shí)現(xiàn) MVCC 時(shí)用到的一致性讀視圖,即 consistent read view,用于支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復(fù)讀)隔離級(jí)別的實(shí)現(xiàn)。
在可重復(fù)讀隔離級(jí)別下,事務(wù)在啟動(dòng)的時(shí)候就“拍了個(gè)快照”。
MySQL的MVCC快照并不是每一個(gè)事務(wù)進(jìn)來(lái)就copy一份數(shù)據(jù)庫(kù)信息,而是基于數(shù)據(jù)表每行信息后面保存的系統(tǒng)版本號(hào)去實(shí)現(xiàn)的。如下圖所示,一行信息會(huì)有多個(gè)版本并存,每個(gè)事務(wù)可能讀取到的版本不一樣
InnoDB 里面每個(gè)事務(wù)有一個(gè)唯一的事務(wù) ID,叫作 transaction id。它是在事務(wù)開(kāi)始的時(shí)候向 InnoDB 的事務(wù)系統(tǒng)申請(qǐng)的,是按申請(qǐng)順序嚴(yán)格遞增的。
而每行數(shù)據(jù)也都是有多個(gè)版本的。每次事務(wù)更新數(shù)據(jù)的時(shí)候,都會(huì)生成一個(gè)新的數(shù)據(jù)版本,并且把 transaction id 賦值給這個(gè)數(shù)據(jù)版本的row trx_id 。同時(shí),舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它 。
數(shù)據(jù)表中的一行記錄,其實(shí)可能有多個(gè)版本 (row),每個(gè)版本有自己的 row trx_id。 就是一個(gè)記錄被多個(gè)事務(wù)連續(xù)更新后的狀態(tài)。
圖中虛線框里是同一行數(shù)據(jù)的 4 個(gè)版本,當(dāng)前最新版本是 V4,k 的值是 22,它是被 transaction id 為 25 的事務(wù)更新的,因此它的 row trx_id 也是 25。
語(yǔ)句更新會(huì)生成 undo log(回滾日志)嗎?那么,undo log 在哪呢?
實(shí)際上,圖 2 中的三個(gè)虛線箭頭,就是 undo log;而 V1、V2、V3 并不是物理上真實(shí)存在的,而是每次需要的時(shí)候根據(jù)當(dāng)前版本和 undo log 計(jì)算出來(lái)的。比如,需要 V2 的時(shí)候,就是通過(guò) V4 依次執(zhí)行 U3、U2 算出來(lái)。
按照可重復(fù)讀的定義,一個(gè)事務(wù)啟動(dòng)的時(shí)候,能夠看到所有已經(jīng)提交的事務(wù)結(jié)果。但是之后,這個(gè)事務(wù)執(zhí)行期間,其他事務(wù)的更新對(duì)它不可見(jiàn)。因此,一個(gè)事務(wù)只需要在啟動(dòng)的時(shí)候聲明說(shuō),“以我啟動(dòng)的時(shí)刻為準(zhǔn),如果一個(gè)數(shù)據(jù)版本是在我啟動(dòng)之前生成的,就認(rèn);如果是我啟動(dòng)以后才生成的,我就不認(rèn),我必須要找到它的上一個(gè)版本”。當(dāng)然,如果“上一個(gè)版本”也不可見(jiàn),那就得繼續(xù)往前找。還有,如果是這個(gè)事務(wù)自己更新的數(shù)據(jù),它自己還是要認(rèn)的。
當(dāng)有多個(gè)請(qǐng)求來(lái)讀取表中的數(shù)據(jù)時(shí)可以不采取任何操作,但是多個(gè)請(qǐng)求里有讀請(qǐng)求,又有修改請(qǐng)求時(shí)必須有一種措施來(lái)進(jìn)行并發(fā)控制。不然很有可能會(huì)造成不一致。 讀寫(xiě)鎖 解決上述問(wèn)題很簡(jiǎn)單,只需用兩種鎖的組合來(lái)對(duì)讀寫(xiě)請(qǐng)求進(jìn)行控制即可,
這兩種鎖被稱(chēng)為:
共享鎖(shared lock),又叫做"讀鎖" 讀鎖是可以共享的,或者說(shuō)多個(gè)讀請(qǐng)求可以共享一把鎖讀數(shù)據(jù),不會(huì)造成阻塞。
排他鎖(exclusive lock),又叫做"寫(xiě)鎖" 寫(xiě)鎖會(huì)排斥其他所有獲取鎖的請(qǐng)求,一直阻塞,直到寫(xiě)入完成釋放鎖。
總結(jié): 通過(guò)讀寫(xiě)鎖,可以做到讀讀可以并行,但是不能做到寫(xiě)讀,寫(xiě)寫(xiě)并行 事務(wù)的隔離性就是根據(jù)讀寫(xiě)鎖來(lái)實(shí)現(xiàn)的?。。?/p>
“MySQL事務(wù)工作流程原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識(shí)可以關(guān)注創(chuàng)新互聯(lián)網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實(shí)用文章!