1. 程式人生 > >1009MySQL數據庫InnoDB存儲引擎Log漫遊

1009MySQL數據庫InnoDB存儲引擎Log漫遊

系統故障 死鎖問題 是否 當前 ace 包括 超過 隨著 ani

00 – Undo Log
Undo Log 是為了實現事務的原子性,在MySQL數據庫InnoDB存儲引擎中,還用Undo Log來實現多版本並發控制(簡稱:MVCC)。

- 事務的原子性(Atomicity)
事務中的所有操作,要麽全部完成,要麽不做任何操作,不能只做部分操作。如果在執行的過程中發生
了錯誤,要回滾(Rollback)到事務開始前的狀態,就像這個事務從來沒有執行過。

- 原理
Undo Log的原理很簡單,為了滿足事務的原子性,在操作任何數據之前,首先將數據備份到一個地方
(這個存儲數據備份的地方稱為Undo Log)。然後進行數據的修改。如果出現了錯誤或者用戶執行了
ROLLBACK語句,系統可以利用Undo Log中的備份將數據恢復到事務開始之前的狀態。

除了可以保證事務的原子性,Undo Log也可以用來輔助完成事務的持久化。

- 事務的持久性(Durability)
事務一旦完成,該事務對數據庫所做的所有修改都會持久的保存到數據庫中。為了保證持久性,數據庫
系統會將修改後的數據完全的記錄到持久的存儲上。

- 用Undo Log實現原子性和持久化的事務的簡化過程
假設有A、B兩個數據,值分別為1,2。
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄B=2到undo log.
E.修改B=4.
F.將undo log寫到磁盤。
G.將數據寫到磁盤。
H.事務提交
這裏有一個隱含的前提條件:‘數據都是先讀到內存中,然後修改內存中的數據,最後將數據寫回磁盤’。

之所以能同時保證原子性和持久化,是因為以下特點:
A. 更新數據前記錄Undo log。
B. 為了保證持久性,必須將數據在事務提交前寫到磁盤。只要事務成功提交,數據必然已經持久化。
C. Undo log必須先於數據持久化到磁盤。如果在G,H之間系統崩潰,undo log是完整的,
可以用來回滾事務。
D. 如果在A-F之間系統崩潰,因為數據沒有持久化到磁盤。所以磁盤上的數據還是保持在事務開始前的狀態。

缺陷:每個事務提交前將數據和Undo Log寫入磁盤,這樣會導致大量的磁盤IO,因此性能很低。

如果能夠將數據緩存一段時間,就能減少IO提高性能。但是這樣就會喪失事務的持久性。因此引入了另外一
種機制來實現持久化,即Redo Log.

01 – Redo Log

- 原理
和Undo Log相反,Redo Log記錄的是新數據的備份。在事務提交前,只要將Redo Log持久化即可,
不需要將數據持久化。當系統崩潰時,雖然數據沒有持久化,但是Redo Log已經持久化。系統可以根據
Redo Log的內容,將所有數據恢復到最新的狀態。

- Undo + Redo事務的簡化過程
假設有A、B兩個數據,值分別為1,2.
A.事務開始.
B.記錄A=1到undo log.
C.修改A=3.
D.記錄A=3到redo log.
E.記錄B=2到undo log.
F.修改B=4.
G.記錄B=4到redo log.
H.將redo log寫入磁盤。
I.事務提交

- Undo + Redo事務的特點
A. 為了保證持久性,必須在事務提交前將Redo Log持久化。
B. 數據不需要在事務提交前寫入磁盤,而是緩存在內存中。
C. Redo Log 保證事務的持久性。
D. Undo Log 保證事務的原子性。
E. 有一個隱含的特點,數據必須要晚於redo log寫入持久存儲。

- IO性能
Undo + Redo的設計主要考慮的是提升IO性能。雖說通過緩存數據,減少了寫數據的IO.
但是卻引入了新的IO,即寫Redo Log的IO。如果Redo Log的IO性能不好,就不能起到提高性能的目的。
為了保證Redo Log能夠有比較好的IO性能,InnoDB 的 Redo Log的設計有以下幾個特點:

A. 盡量保持Redo Log存儲在一段連續的空間上。因此在系統第一次啟動時就會將日誌文件的空間完全分配。
以順序追加的方式記錄Redo Log,通過順序IO來改善性能。
B. 批量寫入日誌。日誌並不是直接寫入文件,而是先寫入redo log buffer.當需要將日誌刷新到磁盤時
(如事務提交),將許多日誌一起寫入磁盤.
C. 並發的事務共享Redo Log的存儲空間,它們的Redo Log按語句的執行順序,依次交替的記錄在一起,
以減少日誌占用的空間。例如,Redo Log中的記錄內容可能是這樣的:
記錄1: <trx1, insert …>
記錄2: <trx2, update …>
記錄3: <trx1, delete …>
記錄4: <trx3, update …>
記錄5: <trx2, insert …>
D. 因為C的原因,當一個事務將Redo Log寫入磁盤時,也會將其他未提交的事務的日誌寫入磁盤。
E. Redo Log上只進行順序追加的操作,當一個事務需要回滾時,它的Redo Log記錄也不會從
Redo Log中刪除掉。

02 – 恢復(Recovery)

- 恢復策略
前面說到未提交的事務和回滾了的事務也會記錄Redo Log,因此在進行恢復時,這些事務要進行特殊的
的處理.有2中不同的恢復策略:

A. 進行恢復時,只重做已經提交了的事務。
B. 進行恢復時,重做所有事務包括未提交的事務和回滾了的事務。然後通過Undo Log回滾那些
未提交的事務。

- InnoDB存儲引擎的恢復機制
MySQL數據庫InnoDB存儲引擎使用了B策略, InnoDB存儲引擎中的恢復機制有幾個特點:

A. 在重做Redo Log時,並不關心事務性。 恢復時,沒有BEGIN,也沒有COMMIT,ROLLBACK的行為。
也不關心每個日誌是哪個事務的。盡管事務ID等事務相關的內容會記入Redo Log,這些內容只是被當作
要操作的數據的一部分。
B. 使用B策略就必須要將Undo Log持久化,而且必須要在寫Redo Log之前將對應的Undo Log寫入磁盤。
Undo和Redo Log的這種關聯,使得持久化變得復雜起來。為了降低復雜度,InnoDB將Undo Log看作
數據,因此記錄Undo Log的操作也會記錄到redo log中。這樣undo log就可以象數據一樣緩存起來,
而不用在redo log之前寫入磁盤了。
包含Undo Log操作的Redo Log,看起來是這樣的:
記錄1: <trx1, Undo log insert <undo_insert …>>
記錄2: <trx1, insert …>
記錄3: <trx2, Undo log insert <undo_update …>>
記錄4: <trx2, update …>
記錄5: <trx3, Undo log insert <undo_delete …>>
記錄6: <trx3, delete …>
C. 到這裏,還有一個問題沒有弄清楚。既然Redo沒有事務性,那豈不是會重新執行被回滾了的事務?
確實是這樣。同時Innodb也會將事務回滾時的操作也記錄到redo log中。回滾操作本質上也是
對數據進行修改,因此回滾時對數據的操作也會記錄到Redo Log中。
一個回滾了的事務的Redo Log,看起來是這樣的:
記錄1: <trx1, Undo log insert <undo_insert …>>
記錄2: <trx1, insert A…>
記錄3: <trx1, Undo log insert <undo_update …>>
記錄4: <trx1, update B…>
記錄5: <trx1, Undo log insert <undo_delete …>>
記錄6: <trx1, delete C…>
記錄7: <trx1, insert C>
記錄8: <trx1, update B to old value>
記錄9: <trx1, delete A>
一個被回滾了的事務在恢復時的操作就是先redo再undo,因此不會破壞數據的一致性.

03 – 日誌的內容

- 數據是什麽
從不同的角度和層次來看,我們可以將數據庫中的數據看作:
A. 關系數據
B. 元組或對象
C. 存在Page中的二進制序列

因此Log中也可以記錄不同的內容:
- 物理的日誌(Physical Log)
A. 記錄完整的Page
B. 記錄Page中被修改的部分(page中的偏移,內容和長度).

優點:因為恢復時,完全不依賴原頁面上的內容,所以不要求持久化了的數據保持在一個一致的狀態。
比如在寫一個頁面到磁盤上時,系統發生故障,頁面上的一部數據寫入了磁盤,另一部分丟失了。
這時仍然可以恢復出正確的數據。

缺點:Log記錄的內容很多,占用很大的空間。如B-Tree的分裂操作,要記錄約一個完整Page的內容。

- 邏輯的日誌(Logical Log)
記錄在關系(表)上的一個元組操作。
A. 插入一行記錄。
B. 修改一行記錄。
C. 刪除一行記錄。
邏輯日誌比起物理的日誌,顯得簡潔的多。而且占用的空間也要小的多。
但是邏輯日誌有2個缺點:
A. 部分執行
例如:表T有2個索引,在向T插入1條記錄時,需要分別向2個B-Tree中插入記錄。
有可能第一個B-Tree插入成功了,但是第二個B-Tree沒有插入成功。在恢復或
回滾時,需要處理這些特殊情況。
B. 操作的一致性問題
一個插入操作有一個B-Tree的分裂,頁A的一半數據移到了B頁,A頁寫入了磁盤,B頁沒有寫入磁盤。
如果這時候發生了故障,需要進行恢復,邏輯日誌是很難搞定的。

邏輯的日誌上的‘部分執行’的問題是比較好維護的,但是‘一致性’的問題維護起來是很復雜的。

- 物理和邏輯結合的日誌(Physiological Log)
這種日誌將物理和邏輯日誌相結合,取其利,去其害。從而達到一個相對更好的一個狀態。這種日誌有2個特點:
A. 物理到page. 將操作細分到頁級別。為每個頁上的操作單獨記日誌。
比如,一個Insert分別在2個B-Tree的節點上做了插入操作,那麽就分別為每一個頁的操作記錄一條日誌。
B. Page內采用邏輯的日誌。比如對一個B-Tree的頁內插入一條記錄時,物理上來說要修改Page Header的
內容(如,頁內的記錄數要加1),要插入一行數據到某個位置,要修改相鄰記錄裏的鏈表指針,要修改Slot的
屬性等。從邏輯上來說,就是在這個頁內插入了一行記錄。因此Page內的邏輯日誌只記錄:’這是一個
插入操作’和’這行數據的內容‘。

MySQL數據庫InnoDB存儲引擎的Redo Log 記錄的就是這種物理和邏輯相結合的日誌。
使用頁內的邏輯日誌,可以減少日誌占用的空間。但是它畢竟還是邏輯日誌,上面提到的2個問題能夠避免嗎?
A. 頁面內的部分執行的情況可以認為不存在了。因為整個頁面的操作是原子操作,在完成之前是不會寫
到磁盤上的。
B. 操作一致性的問題仍然存在。如果在寫一個Page到磁盤時發生了故障,可能導致Page Header的記
錄數被加1了,但是數據沒有刷新到磁盤上,總之頁面上的數據不一致了。

好在這個問題被縮小到了一個頁面的範圍內,因此比較容易解決。InnoDB存儲引擎中用Double Write的方法
來解決這個問題。

- Double Write
Double Write的思路很簡單:
A. 在覆蓋磁盤上的數據前,先將Page的內容寫入到磁盤上的其他地方(InnoDB存儲引擎中的doublewrite
buffer,這裏的buffer不是內存空間,是持久存儲上的空間).
B. 然後再將Page的內容覆蓋到磁盤上原來的數據。

如果在A步驟時系統故障,原來的數據沒有被覆蓋,還是完整的。
如果在B步驟時系統故障,原來的數據不完整了,但是新數據已經被完整的寫入了doublewrite buffer.
因此系統恢復時就可以用doublewrite buffer中的新Page來覆蓋這個不完整的page.

Double write 顯然會曾加磁盤的IO。直覺上IO次數增加了1倍,但是性能損失並不是很大。Peter在
innodb-double-write中說性能損失不超過5-10%。應該是因為多數情況下使用了批量寫入的緣故。
A. Double write buffer是一段連續的存儲空間,可以順序寫入。
B. Double write有自己的寫buffer.
C. 先將多個要做doublewrite的page寫入內存的buffer,然後再一起寫到磁盤上。

代碼在:buf0dblwr.cc
buf_flush_write_block_low()調用
buf_dblwr_write_single_page()或 buf_dblwr_add_to_batch()來實現doublewrite.

- Checksum
檢測頁面是否一致的功能是靠Checksum來完成的,每個頁面修改完成後都會記算一個頁面的checksum。
這個checksum存放在頁面的尾部.每次從磁盤讀一個頁到內存時,都需要檢測頁的一致性。
函數buf_page_is_corrupted()是用來檢測page的一致性的.

- InnoDB Redo Log的日誌類型
InnoDB redo log的格式可以概括為:
<Space ID>+<Page NO.>+<操作類型>+<數據>.

Redo Log記錄的頁面操作大致可以分為以下幾種類型:
A. 在頁面上寫入N個字節的內容,這些可以看作是物理的Log.
MLOG_1BYTE, MLOG_2BYTES, MLOG_4BYTES, MLOG_8BYTES, MLOG_WRITE_STRING
各種Page鏈表的指針修改,以及文件頭,段頁等的內容的修改都是以這種方式記錄的日誌。
B. 頁面上的記錄操作。
MLOG_REC_*, MLOG_LIST_*, MLOG_COMP_REC_*, MLOG_COMP_LIST_*
這些日誌記錄了對B-Tree頁的INSER, DELETE, UPDATE操作和分裂合並操作。
C. 文件和Page操作
MLOG_FILE_CREATE, MLOG_FILE_RENAME, MLOG_FILE_DELETE,
MLOG_PAGE_CREATE, MLOG_INIT_FILE_PAGE, MLOG_PAGE_REORGANIZE
D. Undo Log操作
MLOG_UNDO_*
InnoDB中將undo log的操作也記入了redo log. 為什麽要這樣做,在前面‘恢復’已經說了.

這裏只提到了部分Redo Log的類型,完整的定義在mtr0mtr.h文件中. 通過這個類型的定義,可以
很容易的找到都在哪些地方使用了。

雖說Redo Log將數據的操作細分到了頁面級別。但是有些在多個頁面上的操作是邏輯上不可分裂的。
比如B-Tree的分裂操作,對父節點和2個子節點的修改。當進行恢復時,要麽全部恢復,要麽全部不
恢復,不能只恢復其中的部分頁面。InnoDB中通過mini-transaction(MTR)來保證這些不可再分
的操作的原子性。

- InnoDB Undo Log的日誌類型
MySQL數據庫InnoDB存儲引擎的undo log采用了邏輯的日誌。
InnoDB undo log的格式可以概括為:<操作類型>+<Table ID>+<數據>.

A. 從表中刪除一行記錄
TRX_UNDO_DEL_MARK_REC(將主鍵記入日誌)
在刪除一條記錄時,並不是真正的將數據從數據庫中刪除,只是標記為已刪除.這樣做的好處是
Undo Log中不用記錄整行的信息.在undo時操作也變得很簡單.
B. 向表中插入一行記錄
TRX_UNDO_INSERT_REC(將主鍵記入日誌)
TRX_UNDO_UPD_DEL_REC(緊將主鍵記入日誌) 當表中有一條被標記為刪除的記錄和要插入的
數據主鍵相同時, 實際的操作是更新這個被標記為刪除的記錄。
C. 更新表中的一條記錄
TRX_UNDO_UPD_EXIST_REC(將主鍵和被更新了的字段內容記入日誌)
TRX_UNDO_DEL_MARK_REC和TRX_UNDO_INSERT_REC,當更新主鍵時,實際執行的過程
是刪除舊的記錄然後,再插入一條新的記錄。

因為undo log還要被MVCC和Purge使用,所以還有TRX_ID和DATA_ROLL_PTR等特殊的內容記錄
在日誌中。TRX_UNDO_INSERT_REC不需要記錄這些內容.因為MVCC中不可內引用一個不存在的數據。
這也是事務將insert和update、delete的undo log分開存放的原因。事務提交後,insert的undo
占用的空間就可以立即釋放了.

這些類型定義在:trx0rec.h.
記錄日誌的過程在:trx_undo_page_report_insert()和trx_undo_page_report_modify()中。
Undo操作在row0undo.c, row0uins.c和row0umod.c中, 入口函數是row_undo().

- 邏輯日誌的一致性問題
前面說了邏輯日誌的一致性問題是很復雜的,為什麽undo log要用邏輯日誌呢?
因為redo log使用了physiological日誌和MTR,就可以保證在恢復時重做完redo log後,
數據是一致。在執行undo時,就不必考慮這個問題了。

04 – Checkpoint

理論上來說,如果MySQL數據庫InnoDB存儲引擎的buffer足夠大,就不需要將數據本身持久化。將全部的redo log重新執行一遍
就可以恢復所有的數據。但是隨著時間的積累,Redo Log會變的很大很大。如果每次都從第一條記
錄開始恢復,恢復的過程就會很慢,從而無法被容忍。為了減少恢復的時間,就引入了Checkpoint機制。

- 臟頁(dirty page)
如果一個數據頁在內存中修改了,但是還沒有刷新到磁盤。這個數據頁就稱作臟頁。

- 日誌順序號(Log Sequence Number)
LSN是日誌空間中每條日誌的結束點,用字節偏移量來表示。在Checkpoint和恢復時使用。

- 原理
假設在某個時間點,所有的臟頁都被刷新到了磁盤上.這個時間點之前的所有Redo Log就不需要重
做了。系統記錄下這個時間點時redo log的結尾位置作為checkpoint. 在進行恢復時,從這個
checkpoint的位置開始即可。Checkpoint點之前的日誌也就不再需要了,可以被刪除掉。為了
更好的利用日誌空間,InnoDB以環形緩存(circular buffer)的方式來使用日誌空間。

技術分享

Sharp Checkpoint

- Sharp Checkpoint
對於繁忙的系統來說,很少會出現這樣的的一個時間點。為了能創造出這樣一個時間點,最簡單的辦
法就是,在某個時間開始停止一切更新操作,直到所有的臟頁被刷新到磁盤,Checkpoint被記錄。
顯然對於繁忙的系統, 這種方法是不合適的。能不能在checkpoint時不停止用戶的操作呢?

- Fuzzy Checkpoint
如下圖所示,如果刷臟頁的同時用戶還在更新數據,LSN1前的某個臟頁在刷到持久存儲之前就有可能被
LSN1之後的某個操作給修改了。當checkpoint完成時,LSN1後的部分操作(R1,R2對應的操作)也被
持久化了。當Sharp checkpoint完成時,持久存儲中存儲的數據是某個確切時間點的內存數據的快照。
Fuzzy checkpoint完成時,持久存儲中存儲的數據不是某個確切時間點的內存數據的快照。從某種
程度上,可以說持久存儲中的數據喪失了一致性。在恢復時,必須要解決這個問題。

技術分享

Fuzzy Checkpoint

- 冪等(Idempotence)規則
如上圖所示,checkpoint 在LSN1位置,當checkpoint完成時R1,R2對應的修改也被刷到了持久存儲。
恢復時要從LSN1位置開始,包括R1, R2在內。重新執行後,數據還能正確嗎?
冪等規則要求無論redo log被執行了多少次,數據始終正確。
InnoDB的redo log, 物理到Page,Page內是邏輯日誌。
物理日誌,天然支持冪等規則. 但是邏輯日誌 需要特殊處理,才能支持滿足冪等規則。

- 數據頁的最新(最大)LSN
為了滿足冪等規則,InnoDB中每個數據頁上都記錄有一個LSN。每次更新數據頁時,將LSN修改為
當前操作的redo log的LSN。在恢復時,如果數據頁的LSN大於等於當前redo log的LSN,則跳過此
日誌。

- 異步Checkpoint
實現了冪等規則後,臟頁就可以在任何時間,以任何順序寫入持久存儲了。InnoDB的buffer pool有
一套單獨的機制來刷臟頁。因此很多情況下checkpoint時,並不寫臟頁到存儲。只是將所有臟頁的
最小的LSN記做checkpoint.
checkpoint的實現在log0log.c.
log_checkpoint()實現異步checkpoint.

- 同步Checkpoint
InnoDB的buffer pool通過LRU的算法來決定哪些臟頁應該被寫入持久存儲。如果包含最小LSN的
頁面頻繁的被更新,它就不會被刷到存儲上。這樣就可能導致checkpoint點很長一段時間無法前進,
甚至導致日誌空間被占滿。這時就要按照LSN由最小到大的順序寫一部分臟頁到持久存儲。
log_checkpoint_margin().
log_calc_max_ages()用來計算,‘判斷是否要執行同步checkpoint’用到的參數.

05 – 緩存池(Buffer Pool)
學習到這裏,我更傾向於說這是一個”Redo+Undo+Buffer”的模式。為了提搞IO性能,臟頁緩存在buffer中,
Redo log也要先緩存在內存中,doublewrite也有內存buffer. Buffer pool在這個模式中是至關重要的。

- 頁分類
Buffer pool內的頁分為三種:
A. 未被使用的頁(空白的buffer),沒有映射到一個數據文件中頁。
B. 凈頁,映射到了一個數據文件頁,而且沒有被修改過。內容和數據文件的頁一樣。
C. 臟頁,映射到了一個數據文件頁,並且數據被修改過。內容和數據文件的頁不一樣。

- LRU
InnoDB通過LRU算法來決定哪些臟頁應該被首先寫入磁盤。當空間不足時,哪些凈頁應該被釋放掉。
因此有2個LRU鏈表
A. 臟頁LRU鏈表(buffer_pool->LRU)
B. 凈頁LRU鏈表(unzip_LRU)

- flush_list
同步checkpoint時,需要根據數據頁修改的先後順序來將臟頁寫入持久存儲。因此除了LRU鏈表,
buffer pool中還有一個按臟頁修改先後順序排列的鏈表,叫flush_list.當需要同步checkpoint時,
根據flush_list中頁的順序刷數據到持久存儲。
A. 一個頁只在flush_list中出現1次,因為一個頁面只需要寫一次。
B. 按頁面最早一次被修改的順序排列。

06 – Mini-Transaction(MTR)
前面提到Redo Log將數據的操作細分到了頁面級別。但是有些在多個頁面上的操作是邏輯上不可分裂的。
InnoDB中用Mini-Transaction來表示這些不可再細分的邏輯操作。

- MTR的一致性
為了滿足MTR的一致性,MTR做了如下的設計:
A. MTR的所有日誌被封裝在一起,當MTR提交時一起寫入redo log buffer.
這樣做有2個好處:
* 減少並發MTR對redo log buffer 的競爭。
* 連續的存儲在一起,恢復時的處理過程更簡單。
B. InnoDB在redo log的層面,將一個MTR中的所有日誌作為Redo log的最小單元。在恢復時,一個MTR
中的所有日誌必須是完整的才能進行恢復。

- MTR日誌的封裝
為了在日誌文件中區分不同的MTR,MTR將MLOG_SINGLE_REC_FLAG或MLOG_MULTI_REC_END寫入
redo log(mtr_log_reserve_and_write()).
A. 如果MTR的日誌中只有一行記錄,在日誌的開始處添加MLOG_SINGLE_REC_FLAG,表示MTR中只有
一條記錄。
B. 如果MTR的日誌中有多行記錄,在日誌的結尾處添加一個類型為MLOG_MULTI_REC_END的日誌,
代表MTR的日誌到此結束.

- MTR的LSN
A. 因為在將日誌寫入redo log buffer時,才能獲得LSN。所以修改數據時,並沒有修改頁上的LSN。
需要在MTR獲得LSN後統一修改。
B. 一個MTR只有一個LSN. 一個MTR內修改的所有頁的LSN相同。這樣checkpoint就不會出現在MTR的中間。
C. 在獲得LSN後,如果被MTR修改的臟頁不在buffer pool的flush_list裏,就會被添加進去。

看mtr_memo_slot_note_modification()和buf_flush_note_modification().

- 頁級鎖
提交時才寫日誌到redo log的做法,決定了MTR要使用頁級鎖。
A. 一個頁面不能同時被多個活動的MTR修改。
B. MTR中數據頁的鎖,直到MTR提交時(日誌寫入redo log buffer)後才釋放。

鎖對象存儲在mtr的memo中。調用mtr_s_lock和mtr_x_lock來加鎖時,鎖對象被保存到memo中。
解鎖在mtr_memo_slot_release()中完成。

- MTR的ROLLBACK
看完MTR的代碼發現mtr沒有記錄undo日誌,也不能rollback. MTR都是很小的操作單元,而且每個MTR
都有明確的操作目標,因此比較容易保證其正確性。
A. 因為頁面操作是在內存中完成,並且頁面有固定的格式,因此很多的頁面操作是不會失敗的。
InnoDB存儲引擎中的很多寫頁面的函數都沒有返回值.
B. 在對任何頁面操作前,先要檢查是否可能發生錯誤。如果可能發生錯誤就不能往下執行。
如,當插入一行記錄到B-Tree的節點時,首先檢查頁面有足夠的空間。
C. 使用更大粒度的鎖(如B-Tree的鎖),並且按照一定的順序加鎖。這樣才能不導致死鎖問題。

以上是自己看代碼後的大概印象,不一定說到了正點上。MTR模塊的代碼雖簡單,但是MTR在其他模塊大量的
使用。要透徹的理解MTR,估計還得要看其他模塊的代碼,整理出來大部分MTR操作過程才行.

1009MySQL數據庫InnoDB存儲引擎Log漫遊