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