溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務(wù)條款》

MySQL事務(wù)工作流程原理是什么

發(fā)布時間:2022-06-01 13:51:25 來源:億速云 閱讀:131 作者:iii 欄目:MySQL數(shù)據(jù)庫

本篇內(nèi)容介紹了“MySQL事務(wù)工作流程原理是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!

MySQL事務(wù)工作流程原理是什么

  • 事務(wù)的原子性是通過 undo log 來實現(xiàn)的

  • 事務(wù)的持久性是通過 redo log 來實現(xiàn)的

  • 事務(wù)的隔離性是通過 (讀寫鎖+MVCC)來實現(xiàn)的

  • 而事務(wù)的終極大 boss 一致性是通過原子性,持久性,隔離性來實現(xiàn)的?。?!

MySQL事務(wù)工作流程原理是什么

1、redo log 實現(xiàn)持久性

問題1: 為什么需要redo log?

InnoDB作為MySQL的存儲引擎,數(shù)據(jù)是存放在磁盤中的,但如果每次讀寫數(shù)據(jù)都需要磁盤IO,效率會很低。為此,InnoDB提供了緩存(Buffer Pool),作為訪問數(shù)據(jù)庫的緩沖:當(dāng)從數(shù)據(jù)庫讀取數(shù)據(jù)時,會首先從Buffer Pool中讀取,如果Buffer Pool中沒有,則從磁盤讀取后放入Buffer Pool;當(dāng)向數(shù)據(jù)庫寫入數(shù)據(jù)時,會首先寫入Buffer Pool,Buffer Pool中修改的數(shù)據(jù)會定期刷新到磁盤中 。

Buffer Pool的使用大大提高了讀寫數(shù)據(jù)的效率,但是也帶了新的問題:如果MySQL宕機,而此時Buffer Pool中修改的數(shù)據(jù)還沒有刷新到磁盤,就會導(dǎo)致數(shù)據(jù)的丟失,事務(wù)的持久性無法保證。

問題2:redo log如何保證事務(wù)的持久性?

Redo log可以簡單分為以下兩個部分:

一是內(nèi)存中重做日志緩沖 (redo log buffer),是易失的,在內(nèi)存中

二是重做日志文件 (redo log file),是持久的,保存在磁盤中

這里再細(xì)說下寫入Redo的時機:

在數(shù)據(jù)頁修改完成之后,在臟頁刷出磁盤之前,寫入redo日志。注意的是先修改數(shù)據(jù),后寫日志

redo日志比數(shù)據(jù)頁先寫回磁盤

聚集索引、二級索引、undo頁面的修改,均需要記錄Redo日志

MySQL事務(wù)工作流程原理是什么

在 MySQL中,如果每一次的更新操作都需要寫進磁盤,然后磁盤也要找到對應(yīng)的那條記錄,然后再更新,整個過程 IO 成本、查找成本都很高。為了解決這個問題,MySQL 的設(shè)計者就采用了日志(redo log)來提升更新效率。

當(dāng)事務(wù)提交時,先將 redo log buffer 寫入到 redo log file 進行持久化,待事務(wù)的commit操作完成時才算完成。這種做法也被稱為 Write-Ahead Log(預(yù)先日志持久化),在持久化一個數(shù)據(jù)頁之前,先將內(nèi)存中相應(yīng)的日志頁持久化。

具體來說,當(dāng)有一條記錄需要更新的時候,InnoDB 引擎就會先把記錄寫到 redo log(redo log buffer)里面,并更新內(nèi)存(buffer pool),這個時候更新就算完成了。同時,InnoDB 引擎會在適當(dāng)?shù)臅r候(如系統(tǒng)空閑時),將這個操作記錄更新到磁盤里面(刷臟頁)。

在一個事務(wù)中可以修改多個頁,Write-Ahead Log 可以保證單個數(shù)據(jù)頁的一致性,但是無法保證事務(wù)的持久性,F(xiàn)orce-log-at-commit 要求當(dāng)一個事務(wù)提交時,其產(chǎn)生所有的mini-transaction 日志必須刷新到磁盤中,若日志刷新完成后,在緩沖池中的頁刷新到持久化存儲設(shè)備前數(shù)據(jù)庫發(fā)生了宕機,那么數(shù)據(jù)庫重啟時,可以通過日志來保證數(shù)據(jù)的完整性。

問題3:重寫日志的流程?

MySQL事務(wù)工作流程原理是什么

上圖表示了重做日志的寫入流程,每個mini-transaction對應(yīng)每一條DML操作,比如一條update語句,其由一個mini-transaction來保證,對數(shù)據(jù)修改后,產(chǎn)生redo1,首先將其寫入mini-transaction私有的Buffer中,update語句結(jié)束后,將redo1從私有Buffer拷貝到公有的Log Buffer中。當(dāng)整個外部事務(wù)提交時,將redo log buffer再刷入到redo log file中。( redo log是按照順序?qū)懭氲?,磁盤的順序讀寫的速度遠(yuǎn)大于隨機讀寫)

問題4:數(shù)據(jù)寫入后的最終落盤,是從 redo log 更新過來的還是從 buffer pool 更新過來的呢?

實際上,redo log 并沒有記錄數(shù)據(jù)頁的完整數(shù)據(jù),所以它并沒有能力自己去更新磁盤數(shù)據(jù)頁,也就不存在由 redo log 更新過去數(shù)據(jù)最終落盤的情況。

① 數(shù)據(jù)頁被修改以后,跟磁盤的數(shù)據(jù)頁不一致,稱為臟頁。最終數(shù)據(jù)落盤,就是把內(nèi)存中的數(shù)據(jù)頁寫盤。這個過程與 redo log 毫無關(guān)系。

② 在崩潰恢復(fù)場景中,InnoDB 如果判斷到一個數(shù)據(jù)頁可能在崩潰恢復(fù)的時候丟失了更新,就會將它讀到內(nèi)存,然后讓 redo log 更新內(nèi)存內(nèi)容。更新完成后,內(nèi)存頁變成臟頁,就回到了第一種情況的狀態(tài)

問題5:redo log buffer 是什么?是先修改內(nèi)存,還是先寫 redo log 文件?

在一個事務(wù)的更新過程中,日志是要寫多次的。比如下面這個事務(wù):

Copybegin;

INSERT INTO T1 VALUES ('1', '1');

INSERT INTO T2 VALUES ('1', '1');

commit;

這個事務(wù)要往兩個表中插入記錄,插入數(shù)據(jù)的過程中,生成的日志都得先保存起來,但又不能在還沒 commit 的時候就直接寫到 redo log 文件里。

因此就需要 redo log buffer 出場了,它就是一塊內(nèi)存,用來先存 redo 日志的。也就是說,在執(zhí)行第一個 insert 的時候,數(shù)據(jù)的內(nèi)存被修改了,redo log buffer 也寫入了日志。

但是,真正把日志寫到 redo log 文件,是在執(zhí)行 commit 語句的時候做的。

redo log buffer 本質(zhì)上只是一個 byte 數(shù)組,但是為了維護這個 buffer 還需要設(shè)置很多其他的 meta data,這些 meta data 全部封裝在 log_t 結(jié)構(gòu)體中。

問題6:redo log順序?qū)懭氪疟P?

redo log以順序的方式寫入文件,當(dāng)全部文件寫滿的時候則回到第一個文件相應(yīng)的起始位置進行覆蓋寫,每次提交事務(wù)之后,都先將相關(guān)的操作日志寫入redo日志文件中,并且都追加到文件末尾,這是一個順序I/O

MySQL事務(wù)工作流程原理是什么

圖中展示了一組 4 個文件的 redo log 日志,checkpoint 是當(dāng)前要擦除的位置,擦除記錄前需要先把對應(yīng)的數(shù)據(jù)落盤(更新內(nèi)存頁,等待刷臟頁)。write pos 到 checkpoint 之間的部分可以用來記錄新的操作,如果 write pos 和 checkpoint 相遇,說明 redolog 已滿,這個時候數(shù)據(jù)庫停止進行數(shù)據(jù)庫更新語句的執(zhí)行,轉(zhuǎn)而進行 redo log 日志同步到磁盤中。checkpoint 到 write pos 之間的部分等待落盤(先更新內(nèi)存頁,然后等待刷臟頁)。

有了 redo log 日志,那么在數(shù)據(jù)庫進行異常重啟的時候,可以根據(jù) redo log 日志進行恢復(fù),也就達(dá)到了 crash-safe。

redo log 用于保證 crash-safe 能力。innodb_flush_log_at_trx_commit 這個參數(shù)設(shè)置成 1 的時候,表示每次事務(wù)的 redo log 都直接持久化到磁盤。這個參數(shù)建議設(shè)置成 1,這樣可以保證 MySQL 異常重啟之后數(shù)據(jù)不丟失

2、bin log

MySQL 整體來看,其實就有兩塊:一塊是 Server 層,它主要做的是 MySQL 功能層面的事情;還有一塊是引擎層,負(fù)責(zé)存儲相關(guān)的具體事宜。上面我們聊到的 redo log 是 InnoDB 引擎特有的日志,而 Server 層也有自己的日志,稱為 binlog(歸檔日志)

為什么會有兩份日志呢?

因為最開始 MySQL 里并沒有 InnoDB 引擎。MySQL 自帶的引擎是 MyISAM,但是 MyISAM 沒有 crash-safe 的能力,binlog 日志只能用于歸檔。而 InnoDB 是另一個公司以插件形式引入 MySQL 的,既然只依靠 binlog 是沒有 crash-safe 能力的,所以 InnoDB 使用另外一套日志系統(tǒng)——也就是 redo log 來實現(xiàn) crash-safe 能力。

這兩種日志有以下三點不同。

① redo log 是 InnoDB 引擎特有的;binlog 是 MySQL 的 Server 層實現(xiàn)的,所有引擎都可以使用。

② redo log 是物理日志,記錄的是“在某個數(shù)據(jù)頁上做了什么修改”;binlog 是邏輯日志,記錄的是這個語句的原始邏輯,比如“給 ID=2 這一行的 c 字段加 1 ”。

③ redo log 是循環(huán)寫的,空間固定會用完;binlog 是可以追加寫入的?!白芳訉憽笔侵?binlog 文件寫到一定大小后會切換到下一個,并不會覆蓋以前的日志。

MySQL事務(wù)工作流程原理是什么

有了對這兩個日志的概念性理解后,再來看執(zhí)行器和 InnoDB 引擎在執(zhí)行這個 update 語句時的內(nèi)部流程。

① 執(zhí)行器先找引擎取 ID=2 這一行。ID 是主鍵,引擎直接用樹搜索找到這一行。如果 ID=2 這一行所在的數(shù)據(jù)頁本來就在內(nèi)存中,就直接返回給執(zhí)行器;否則,需要先從磁盤讀入內(nèi)存,然后再返回。

② 執(zhí)行器拿到引擎給的行數(shù)據(jù),把這個值加上 1,比如原來是 N,現(xiàn)在就是 N+1,得到新的一行數(shù)據(jù),再調(diào)用引擎接口寫入這行新數(shù)據(jù)。

③ 引擎將這行新數(shù)據(jù)更新到內(nèi)存(InnoDB Buffer Pool)中,同時將這個更新操作記錄到 redo log 里面,此時 redo log 處于 prepare 狀態(tài)。然后告知執(zhí)行器執(zhí)行完成了,隨時可以提交事務(wù)。

④ 執(zhí)行器生成這個操作的 binlog,并把 binlog 寫入磁盤。

⑤ 執(zhí)行器調(diào)用引擎的提交事務(wù)接口,引擎把剛剛寫入的 redo log 改成提交(commit)狀態(tài),更新完成

其中將 redo log 的寫入拆成了兩個步驟:prepare 和 commit,這就是兩階段提交(2PC)

問題1: 兩階段提交原理?

MySQL 使用兩階段提交主要解決 binlog 和 redo log 的數(shù)據(jù)一致性的問題。

兩階段提交原理描述:

① redo log 寫盤,InnoDB 事務(wù)進入 prepare 狀態(tài)。

② 如果前面 prepare 成功,binlog 寫盤,那么再繼續(xù)將事務(wù)日志持久化到 binlog,如果持久化成功,那么 InnoDB 事務(wù)則進入 commit 狀態(tài) 。

redo log 和 binlog 有一個共同的數(shù)據(jù)字段,叫 XID。崩潰恢復(fù)的時候,會按順序掃描 redo log:

① 如果碰到既有 prepare、又有 commit 的 redo log,就直接提交;

② 如果碰到只有 parepare、而沒有 commit 的 redo log,就拿著 XID 去 binlog 找對應(yīng)的事務(wù)。

binlog無記錄,回滾事務(wù)

binlog有記錄,提交事務(wù)

問題2:為什么必須有“兩階段提交”呢?

如果不使用兩階段提交,假設(shè)當(dāng)前 ID=2 的行,字段 c 的值是 0,再假設(shè)執(zhí)行 update 語句過程中在寫完第一個日志后,第二個日志還沒有寫完期間發(fā)生了 crash,會出現(xiàn)什么情況呢?

**先寫 redo log 后寫 binlog。**假設(shè)在 redo log 寫完,binlog 還沒有寫完的時候,MySQL 進程異常重啟。由于我們前面說過的,redo log 寫完之后,系統(tǒng)即使崩潰,仍然能夠把數(shù)據(jù)恢復(fù)回來,所以恢復(fù)后這一行 c 的值是 1。

但是由于 binlog 沒寫完就 crash 了,這時候 binlog 里面就沒有記錄這個語句。因此,之后備份日志的時候,存起來的 binlog 里面就沒有這條語句。

然后你會發(fā)現(xiàn),如果需要用這個 binlog 來恢復(fù)臨時庫的話,由于這個語句的 binlog 丟失,這個臨時庫就會少了這一次更新,恢復(fù)出來的這一行 c 的值就是 0,與原庫的值不同。

**先寫 binlog 后寫 redo log。**如果在 binlog 寫完之后 crash,由于 redo log 還沒寫,崩潰恢復(fù)以后這個事務(wù)無效,所以這一行 c 的值是 0。但是 binlog 里面已經(jīng)記錄了“把 c 從 0 改成 1”這個日志。所以,在之后用 binlog 來恢復(fù)的時候就多了一個事務(wù)出來,恢復(fù)出來的這一行 c 的值就是 1,與原庫的值不同。

可以看到,如果不使用“兩階段提交”,那么數(shù)據(jù)庫的狀態(tài)就有可能和用它的日志恢復(fù)出來的庫的狀態(tài)不一致。

簡單說,redo log 和 binlog 都可以用于表示事務(wù)的提交狀態(tài),而兩階段提交就是讓這兩個狀態(tài)保持邏輯上的一致。

3、undo log 實現(xiàn)原子性

undo log有兩個作用:提供回滾和多版本控制(MVCC)

在數(shù)據(jù)修改的時候,不僅記錄了redo,還記錄了相對應(yīng)的undo,undo log主要記錄的是數(shù)據(jù)的邏輯變化,為了在發(fā)生錯誤時回滾之前的操作,需要將之前的操作都記錄下來,然后在發(fā)生錯誤時才可以回滾。

undo日志,只將數(shù)據(jù)庫邏輯地恢復(fù)到原來的樣子,在回滾的時候,它實際上是做的相反的工作,比如一條INSERT ,對應(yīng)一條 DELETE,對于每個UPDATE,對應(yīng)一條相反的 UPDATE,將修改前的行放回去。undo日志用于事務(wù)的回滾操作進而保障了事務(wù)的原子性。

實現(xiàn)原子性的關(guān)鍵,是當(dāng)事務(wù)回滾時能夠撤銷所有已經(jīng)成功執(zhí)行的sql語句。 InnoDB 實現(xiàn)回滾,靠的是undo log :當(dāng)事務(wù)對數(shù)據(jù)庫進行修改時,InnoDB 會生成對應(yīng)的undo log 如果事務(wù)執(zhí)行失敗或調(diào)用了rollback,導(dǎo)致事務(wù)需要回滾,便可以利用undo log中的信息將數(shù)據(jù)回滾到修改之前的樣子。

在InnoDB存儲引擎中,undo log分為:

insert undo log

update undo log

insert undo log是指在insert 操作中產(chǎn)生的undo log,因為insert操作的記錄,只對事務(wù)本身可見,對其他事務(wù)不可見。故該undo log可以在事務(wù)提交后直接刪除,不需要進行purge操作。

而update undo log記錄的是對delete 和update操作產(chǎn)生的undo log,該undo log可能需要提供MVCC機制,因此不能再事務(wù)提交時就進行刪除。提交時放入undo log鏈表,等待purge線程進行最后的刪除。

補充:purge線程兩個主要作用是:清理undo頁和清除page里面帶有Delete_Bit標(biāo)識的數(shù)據(jù)行。在InnoDB中,事務(wù)中的Delete操作實際上并不是真正的刪除掉數(shù)據(jù)行,而是一種Delete Mark操作,在記錄上標(biāo)識Delete_Bit,而不刪除記錄。是一種"假刪除",只是做了個標(biāo)記,真正的刪除工作需要后臺purge線程去完成。

innodb中通過B+樹作為索引的數(shù)據(jù)結(jié)構(gòu),并且主鍵所在的索引為ClusterIndex(聚簇索引), ClusterIndex中的葉子節(jié)點中保存了對應(yīng)的數(shù)據(jù)內(nèi)容。一個表只能有一個主鍵,所以只能有一個聚簇索引,如果表沒有定義主鍵,則選擇第一個非NULL唯一索引作為聚簇索引,如果還沒有則生成一個隱藏id列作為聚簇索引。

除了Cluster Index外的索引是Secondary Index(輔助索引)。輔助索引中的葉子節(jié)點保存的是聚簇索引的葉子節(jié)點的值。

InnoDB行記錄中除了剛才提到的rowid外,還有trx_id和db_roll_ptr, trx_id表示最近修改的事務(wù)的id,db_roll_ptr指向undo segment中的undo log。

新增一個事務(wù)時事務(wù)id會增加,trx_id能夠表示事務(wù)開始的先后順序。

Undo log分為Insert和Update兩種,delete可以看做是一種特殊的update,即在記錄上修改刪除標(biāo)記。

update undo log記錄了數(shù)據(jù)之前的數(shù)據(jù)信息,通過這些信息可以還原到之前版本的狀態(tài)。

當(dāng)進行插入操作時,生成的Insert undo log在事務(wù)提交后即可刪除,因為其他事務(wù)不需要這個undo log。

進行刪除修改操作時,會生成對應(yīng)的undo log,并將當(dāng)前數(shù)據(jù)記錄中的db_roll_ptr指向新的undo log

4、MVCC實現(xiàn)隔離性

MVCC (MultiVersion Concurrency Control) 叫做多版本并發(fā)控制。

InnoDB的 MVCC ,是通過在每行記錄的后面保存兩個隱藏的列來實現(xiàn)的。這兩個列, 一個保存了行的創(chuàng)建時間,一個保存了行的過期時間, 當(dāng)然存儲的并不是實際的時間值,而是系統(tǒng)版本號。

主要實現(xiàn)思想是通過數(shù)據(jù)多版本來做到讀寫分離。從而實現(xiàn)不加鎖讀進而做到讀寫并行。

MVCC在mysql中的實現(xiàn)依賴的是undo log與read view

  • undo log :undo log 中記錄某行數(shù)據(jù)的多個版本的數(shù)據(jù)。

  • read view :用來判斷當(dāng)前版本數(shù)據(jù)的可見性

MySQL事務(wù)工作流程原理是什么

InnoDB 在實現(xiàn) MVCC 時用到的一致性讀視圖,即 consistent read view,用于支持 RC(Read Committed,讀提交)和 RR(Repeatable Read,可重復(fù)讀)隔離級別的實現(xiàn)。

在可重復(fù)讀隔離級別下,事務(wù)在啟動的時候就“拍了個快照”。

MySQL的MVCC快照并不是每一個事務(wù)進來就copy一份數(shù)據(jù)庫信息,而是基于數(shù)據(jù)表每行信息后面保存的系統(tǒng)版本號去實現(xiàn)的。如下圖所示,一行信息會有多個版本并存,每個事務(wù)可能讀取到的版本不一樣

MySQL事務(wù)工作流程原理是什么

InnoDB 里面每個事務(wù)有一個唯一的事務(wù) ID,叫作 transaction id。它是在事務(wù)開始的時候向 InnoDB 的事務(wù)系統(tǒng)申請的,是按申請順序嚴(yán)格遞增的。

而每行數(shù)據(jù)也都是有多個版本的。每次事務(wù)更新數(shù)據(jù)的時候,都會生成一個新的數(shù)據(jù)版本,并且把 transaction id 賦值給這個數(shù)據(jù)版本的row trx_id 。同時,舊的數(shù)據(jù)版本要保留,并且在新的數(shù)據(jù)版本中,能夠有信息可以直接拿到它 。

數(shù)據(jù)表中的一行記錄,其實可能有多個版本 (row),每個版本有自己的 row trx_id。 就是一個記錄被多個事務(wù)連續(xù)更新后的狀態(tài)。

MySQL事務(wù)工作流程原理是什么

圖中虛線框里是同一行數(shù)據(jù)的 4 個版本,當(dāng)前最新版本是 V4,k 的值是 22,它是被 transaction id 為 25 的事務(wù)更新的,因此它的 row trx_id 也是 25。

語句更新會生成 undo log(回滾日志)嗎?那么,undo log 在哪呢?

實際上,圖 2 中的三個虛線箭頭,就是 undo log;而 V1、V2、V3 并不是物理上真實存在的,而是每次需要的時候根據(jù)當(dāng)前版本和 undo log 計算出來的。比如,需要 V2 的時候,就是通過 V4 依次執(zhí)行 U3、U2 算出來。

按照可重復(fù)讀的定義,一個事務(wù)啟動的時候,能夠看到所有已經(jīng)提交的事務(wù)結(jié)果。但是之后,這個事務(wù)執(zhí)行期間,其他事務(wù)的更新對它不可見。因此,一個事務(wù)只需要在啟動的時候聲明說,“以我啟動的時刻為準(zhǔn),如果一個數(shù)據(jù)版本是在我啟動之前生成的,就認(rèn);如果是我啟動以后才生成的,我就不認(rèn),我必須要找到它的上一個版本”。當(dāng)然,如果“上一個版本”也不可見,那就得繼續(xù)往前找。還有,如果是這個事務(wù)自己更新的數(shù)據(jù),它自己還是要認(rèn)的。

5、MySQL 鎖技術(shù)

當(dāng)有多個請求來讀取表中的數(shù)據(jù)時可以不采取任何操作,但是多個請求里有讀請求,又有修改請求時必須有一種措施來進行并發(fā)控制。不然很有可能會造成不一致。 讀寫鎖 解決上述問題很簡單,只需用兩種鎖的組合來對讀寫請求進行控制即可,

這兩種鎖被稱為:

共享鎖(shared lock),又叫做"讀鎖" 讀鎖是可以共享的,或者說多個讀請求可以共享一把鎖讀數(shù)據(jù),不會造成阻塞。

排他鎖(exclusive lock),又叫做"寫鎖" 寫鎖會排斥其他所有獲取鎖的請求,一直阻塞,直到寫入完成釋放鎖。

MySQL事務(wù)工作流程原理是什么

總結(jié): 通過讀寫鎖,可以做到讀讀可以并行,但是不能做到寫讀,寫寫并行 事務(wù)的隔離性就是根據(jù)讀寫鎖來實現(xiàn)的?。?!

“MySQL事務(wù)工作流程原理是什么”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI