MySQL-14.MySQL事務日誌

长名06發表於2024-06-23

C-14.MySQL事務日誌

事務有4種特性:原子性,一致性,隔離性和永續性。那麼事務的四種特性到底是基於什麼機制實現呢?

  • 事務的隔離性由鎖機制實現。
  • 事務的原子性,一致性和永續性由事務的redo日誌和undo日誌來保證。
    • REDO LOG稱為重做日誌,提供再寫入操作,恢復提交事務修改的頁操作,用來保證事務的永續性。
    • UNDO LOG稱為回滾日誌,回滾行記錄到某個特定版本,用來保證事務的原子性,一致性。

REDO和UNDO本質都可以視為一種恢復操作,但是:

  • redo log:是儲存引擎層(innodb)生成的日誌,記錄的是"物理級別"上頁的修改操作,比如頁號xxx,偏移量yyy寫入了'zzz'資料。主要為了保證資料的可靠性。
  • undo log: 是儲存引擎層(innodb)生成的日誌,記錄的是"邏輯操作"日誌,比如對某一行資料進行了INSERT語句操作,那麼undo log就記錄一條與之相反的DELETE操作。主要用於事務的回滾(undo log 記錄的是每個操作的逆操作)和一致性非鎖定讀(undo log回滾行記錄到某種特定的版本 -- MVCC 即多版本併發控制)。

1.redo日誌


InnoDB儲存引擎是以頁為單位來管理儲存空間的。在真正訪問資料頁之前,需要把在磁碟上的頁快取到記憶體中的Buffer Pool之後,才可以訪問。所有的變更都必須先更新緩衝池中的資料,然後緩衝池中的髒頁會以一定的頻率被刷入磁碟(checkPoint機制),透過緩衝池來最佳化CPU和磁碟之間的鴻溝,這樣就可以保證整體的效能。

1.1 為什麼需要REDO日誌

一方面,緩衝池可以幫助我們消除CPU和磁碟之間的鴻溝,checkpoint機制可以保證資料的最終寫入到磁碟,然而由於checkpoint並不是每次變更的時候就觸發的,而是master執行緒隔一段時間去處理的。所以最壞的情況就是事務提交後,剛寫完緩衝池,資料庫當機了,那麼這段資料就丟失了,無法恢復。

另一方面,事務包含永續性的特性,就是說對於一個已經提交的事務,在事務提交後即使系統發生了崩潰,這個事務對資料庫所作的更改也不會丟失。

那麼如何保證這個永續性呢?一個簡單的做法:在事務提交完成之前把該事務所修改的所有頁面都重新整理到磁碟,但是這個簡單的做法有問題。

  • 修改量與重新整理磁碟工作量嚴重不成正比

有時候我們僅僅修改了某個頁面中的一個位元組,但是我們知道在InnoDB中是以頁為單位來進行磁碟IO的,也就是說我們在該事務提交時不得不將一個完整的頁面從記憶體中重新整理到磁碟,一個頁面預設是16KB,只修改一個位元組,就要重新整理16KB資料到磁碟上顯然是太小題大作了。

  • 隨機IO重新整理較慢

一個事務可能包含很多語句,即使是一條語句也可能修改很多頁面,假如該事務修改的這些頁面可能並不相鄰,這就意味著在將某個事務修改的Buffer Pool中的頁面重新整理到磁碟時,需要進行很多的隨機IO,隨機IO比順序IO要慢,尤其對於傳統的機械硬碟來說。

另一個解決的思路:我們只是想讓已經提交了的事務對資料庫中資料所作的修改永久生效,即使後來系統崩潰,在重啟後也能把這種修改恢復出來。所以我們其實沒有必要在每次事務提交時就把事務在記憶體中修改過的全部頁面重新整理到磁碟,只需要把修改了那些東西記錄一下就好。比如,某個事務將系統表空間中第10號頁面中偏移量為100處的那個位元組的值1改成2。我們只需要記錄一下:將第0號表空間的10號頁面的偏移量為100處的值更新為2。

InnoDB引擎的事務採用了WAL技術(Write-Ahead Logging),這種技術的思想就是先寫日誌,再寫磁碟,只有日誌寫入成功,才算事務提交成功,這裡的日誌就是redo log。當發生當機且資料未刷到磁碟的時候,可以透過redo log來恢復,保證ACID中的D,這就是redo log的作用。

1.2 REDO日誌的好處,特點

1.好處
  • redo日誌降低了刷盤頻率
  • redo日誌佔用的空間非常小

儲存表空間ID、頁號、偏移量(可以簡單的理解,表空間ID + 頁號 + 偏移量 就可以確定修改資料的某些列,這裡我也不在往下搜尋了。)以及需要更新的值,所需的儲存空間是很小的,刷盤快。

2.特點
  • redo日誌是順序寫入磁碟的

在執行事務的過程中,每執行一條語句,就可能產生若干條redo日誌,這些日誌是按照產生的順序寫入磁碟的,也就是使用順序IO,效率比隨機IO快。

  • 事務執行過程中,redo log不斷記錄

redo log跟bin log的區別,redo log是儲存引擎層產生的,而bin log是資料庫層產生的。假設一個事務,對錶做10萬行的記錄插入,在這個過程中,一直不斷的往redo log順序記錄,而bin log不會記錄,直到這個事務提交,才會一次寫入到bin log檔案中。

1.3 redo的組成

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

  • 重做日誌的緩衝(redo log buffer),儲存在記憶體中,是易失的。

在伺服器啟動時就向作業系統申請了一大片稱之為redo log buffer的連續記憶體空間,翻譯成中文就是redo日誌緩衝區。這片記憶體空間被劃分成若干個連續的redo log block。一個redo log block佔用512位元組大小。

引數設定:innodb_log_buffer_size:

redo log buffer大小,預設16M,最大值是4096M,最小值為1M。

mysql> select @@global.innodb_log_buffer_size;#只是全域性變數
+---------------------------------+
| @@global.innodb_log_buffer_size |
+---------------------------------+
|                        16777216 |
+---------------------------------+
1 row in set (0.00 sec)
  • 重做日誌檔案(redo log file),儲存在硬碟中,是持久的。在datadir變數,路徑下,show variables like 'datadir'檢視

REDO日誌檔案如圖所示,其中的ib_logfile0ib_logfile1即位REDO日誌。

1.4 redo的整體流程

以一個更新事務為例,redo log流轉過程,如下圖所示:

第1步:先將原始資料從磁碟中讀入記憶體中來,修改資料複製到記憶體。

第2步:生成一條重做日誌並寫入redo log buffer,記錄的是資料被修改後的值。

第3步:當事務commit時,將redo log buffer中的內容重新整理到redo log file,對redo log file採用追加寫的方式。

第4步:定期將記憶體中修改的資料重新整理到磁碟中。

體會
Write-Ahead Log(預先日誌持久化):在持久化一個資料頁之前,先將記憶體中相應的日誌頁持久化。

1.5 redo log的刷盤策略

redo log的寫入並不是直接寫入磁碟的,InnoDB引擎會在寫redo log的時候先寫redo log buffer,之後以一定的頻率刷入到真正的redo log file中。這裡的一定頻率怎麼看待呢?這就是我們要說的刷盤策略。

注意,redo log buffer刷盤到redo log file的過程並不是真正的刷到磁碟中去,只是刷入到檔案系統快取(page cache)中去(這是現代作業系統為了提高檔案寫入效率做的一個最佳化),真正的寫入會交給系統自己來決定(比如page cache足夠大了)。那麼對於InnoDB來說就存在一個問題,如果交給系統來同步,同樣如果系統當機,那麼資料也丟失了(雖然整個系統當機的機率還是比較小的)。

針對這種情況,InnoDB給出innodb_flush_log_at_trx_commit引數,該引數控制 commit提交事務時,如何將 redo log buffer 中的日誌重新整理到 redo log file 中。它支援三種策略:

  • 設定為0:表示每次事務提交時不進行刷盤操作。(系統預設master thread每隔1s進行一次重做日誌的同步)

  • 設定為1:表示每次事務提交時都將進行同步,刷盤操作(預設值

  • 設定為2:表示每次事務提交時都只把 redo log buffer 內容寫入 page cache,不進行同步。由os自己決定什麼時候同步到磁碟檔案。

mysql> select @@global.innodb_flush_log_at_trx_commit;#global變數
+-----------------------------------------+
| @@global.innodb_flush_log_at_trx_commit |
+-----------------------------------------+
|                                       1 |
+-----------------------------------------+
1 row in set (0.00 sec)

除了後臺執行緒每秒1次的輪詢操作,還有一種情況,當redo log buffer佔用的空間即將達到innodb_log_buffer_size(這個引數預設是16M)的一半的時候,後臺執行緒會主動刷盤。

1.6 不同刷盤策略演示

1.流程圖

小結:innodb_flush_log_at_trx_commit=1
1時,只要事務提交成功,redo log記錄就一定在硬碟裡,不會有任何資料丟失。
如果事務執行期間MySQL服務掛了或系統當機了,這部分日誌丟了,但是事務並沒有提交,所以日誌丟了也不會有損失。可以保證ACID的D,資料絕對不會丟失,但是效率最差的。
建議使用預設值,雖然作業系統當機的機率理論小於資料庫當機的機率,但是一般既然使用了事務,那麼資料的安全相對來說更重要些。

小結innodb_flush_log_at_trx_commit=2
2時,只要事務提交成功,redo log buffer 中的內容只寫入檔案系統快取(page cache)。
如果僅僅只是MySQL掛了不會有任何資料丟失,但是作業系統當機可能會有1秒資料的丟失,這種情況下無法滿足ACID中的D。但是資料2肯定是效率最高的。

小結:innodb_flush_log_at_trx_commit=0
0時,master thread中每1秒進行一次重做日誌的fsync操作,因此例項crash最多丟失1秒鐘內的事務。
(master thread是負責將緩衝池中的資料非同步重新整理到磁碟,保證資料的一致性)
這種情況下,還可能出現事務未提交,就已經寫入redo log buffer和redo file的可能性。
資料0的話,是一種折中的做法,它的IO效率理論是高於1的,低於2的,這種策略也有丟失資料的風險,也無法保證D。

2.舉例
#宋老師,舉例程式碼,未執行,可能會出現時間不一致,正常情況。
#10-事務日誌
USE atguigudb3;

CREATE TABLE test_load(
a INT,
b CHAR(80)
)ENGINE=INNODB;


#建立儲存過程,用於向test_load中新增資料
DELIMITER//
CREATE PROCEDURE p_load(COUNT INT UNSIGNED)
BEGIN
DECLARE s INT UNSIGNED DEFAULT 1;
DECLARE c CHAR(80)DEFAULT REPEAT('a',80);
WHILE s<=COUNT DO
INSERT INTO test_load SELECT NULL,c;
COMMIT;
SET s=s+1;
END WHILE;
END //
DELIMITER;

#測試1:
#設定並檢視:innodb_flush_log_at_trx_commit

SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

#set GLOBAL innodb_flush_log_at_trx_commit = 1;

#呼叫儲存過程
CALL p_load(30000); #1min 28sec

#測試2:
TRUNCATE TABLE test_load;

SELECT COUNT(*) FROM test_load;

SET GLOBAL innodb_flush_log_at_trx_commit = 0;

SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

#呼叫儲存過程
CALL p_load(30000); #37.945 sec

#測試3:
TRUNCATE TABLE test_load;

SELECT COUNT(*) FROM test_load;

SET GLOBAL innodb_flush_log_at_trx_commit = 2;

SHOW VARIABLES LIKE 'innodb_flush_log_at_trx_commit';

#呼叫儲存過程
CALL p_load(30000); #45.173 sec

雖然使用者可以透過設定引數innodb_flush_log_trx_commit為0或2來提高事務提交的效能,但需清楚,這種設定方法喪失了事務的ACID特性。

1.7 寫入redo log buffer過程(瞭解)

該小節,內容不是很理解。

1.補充概念:Mini-Transaction

MySQL把對底層頁面中的一次原子訪問的過程稱之為一個Mini-Transaction,簡稱mtr,比如,向某個索引對應的B+樹中插入一條記錄的過程就是一個Mini-Transaction。一個所謂的mtr可以包含一組redo日誌,在進行崩潰恢復時這一組redo日誌作為一個不可分割的整體。

一個事務可以包含若干條語句,每一條語句其實是由若干個mtr組成,每一個mtr又可以包含若干條redo日誌,畫個圖表示它們的關係就是這樣:

2.redo日誌寫入log buffer

log buffer中寫入redo日誌的過程是順序的,也就是先往前邊的block中寫,當該block的空閒空間用完之後再往下一個block中寫。當我們想往log buffer中寫入redo日誌時,第一個遇到的問題就是應該寫在哪個block的哪個偏移量處,所以InnoDB的設計者特意提供了一個稱之為buf_free的全域性變數,該變數指明後續寫入的redo日誌應該寫入到log buffer中的哪個位置,如圖所示:

一個mtr執行過程中可能產生若干條redo日誌,這些redo日誌是一個不可分割的組,所以其實並不是每生成一條redo日誌,就將其插入到log buffer中,而是每個mtr執行過程中產生的日誌先暫時存到一個地方,當該mtr結束的時候,將過程中產生的一組redo日誌再全部複製到log buffer中。我們現在假設有兩個名為T1、T2的事務,每個事務都包含2個mtr,我們給這幾個mtr命名一下

  • 事務T1的兩個mtr分別稱為mtr_T1_1mtr_T1_2
  • 事務T2的兩個mtr分別稱為mtr_T2_1mtr_T2_2

每個mtr都會產生一組redo日誌,用示意圖來描述—下這些mtr產生的日誌情況:

不同的事務可能是併發執行的,所以T1T2之間的mtr可能是交替執行的。每當一個mtr執行完成時,伴隨該mtr生成的一組redo日誌就需要被複制到log buffer中,也就是說不同事務的mtr可能是交替寫入log buffer的,我們畫個示意圖(為了美觀,我們把一個mtr中產生的所有的redo日誌當作一個整體來畫)︰

有的mtr產生的redo日誌量非常大,比如mtr_t1_2產生的redo日誌佔用空間比較大,佔用了3個block來儲存。

3.redo log block的結構圖

一個redo log block是由日誌頭日誌體日誌尾組成。日誌頭佔用12位元組,日誌尾佔用8位元組,所以一個block真正能儲存的資料就是512-12-8=492位元組。

為什麼一個block設計成512位元組?

這個和磁碟的扇區有關,機械磁碟預設的扇區就是512位元組,如果你要寫入的資料大於512位元組,那麼要寫入的扇區肯定不止一個,這時就要涉及到碟片的轉動,找到下一個扇區,假設現在需要寫入兩個扇區A和B,如果扇區A寫入成功,而扇區B寫入失敗,那麼就會出現非原子性的寫入,而如果每次只寫入和扇區的大小一樣的512位元組,那麼每次的寫入都是原子性的。


1.8redo log file

1.相關引數設定
  • innodb_log_group_home_dir:指定 redo log 檔案組所在的路徑,預設值為 ./ ,表示在資料庫的資料目錄下。MySQL的預設資料目錄(var/lib/mysql)下預設有兩個名為ib_logfile0ib_logfile1的檔案,log buffer中的日誌預設情況下就是重新整理到這兩個磁碟檔案中。此redo日誌檔案位置還可以修改。
  • innodb_log_files_in_group:指明redo log file的個數,命名方式如:ib_logfile0,iblogfile1... iblogfilen。預設2個,最大100個。
mysql> show variables like 'innodb_log_files_in_group';
+---------------------------+-------+
| Variable_name             | Value |
+---------------------------+-------+
| innodb_log_files_in_group | 2     |
+---------------------------+-------+
1 row in set (0.00 sec)
#ib_logfile0
#ib_logfile1
  • innodb_flush_log_at_trx_commit:控制 redo log 重新整理到磁碟的策略,預設為1。
  • innodb_log_file_size:單個 redo log 檔案設定大小,預設值為 48M 。最大值為512G,注意最大值指的是整個 redo log 系列檔案之和,即(innodb_log_files_in_group * innodb_log_file_size )不能大於最大值512G。
mysql> show variables like 'innodb_log_file_size';
+----------------------+----------+
| Variable_name        | Value    |
+----------------------+----------+
| innodb_log_file_size | 50331648 |
+----------------------+----------+
1 row in set (0.00 sec)

根據業務修改其大小,以便容納較大的事務。編輯my.cnf檔案並重啟資料庫生效,如下所示

innodb_log_file_size=200M
2.日誌檔案組


總共的redo日誌檔案大小其實就是:innodb_log_file_size × innodb_log_files_in_group

採用迴圈使用的方式向redo日誌檔案組裡寫資料的話,會導致後寫入的redo日誌覆蓋掉前邊寫的redo日誌?當然!所以InnoDB的設計者提出了checkpoint的概念。

3.checkpoint

在整個日誌檔案組中還有兩個重要的屬性,分別是 write pos、chleckpoint

  • write pos是當前記錄的位置,一邊寫一邊後移
  • checkpoint是當前要擦除的位置,也是往後推移

每次刷盤 redo log記錄到日誌檔案組中,write pos位置就會後移更新。每次MySQL載入日誌檔案組恢復資料時,會清空載入過的redo log記錄,並把 checkpoint後移更新。write pos和checkpoint 之間的還空著的部分可以用來寫入新的redo log記錄。

如果 write pos 追上 checkpoint ,表示日誌檔案組滿了,這時候不能再寫入新的 redo log記錄,MySQL 得停下來,清空一些記錄,把 checkpoint 推進一下。

1.9 redo log 小結

InnoDB的更新操作採用的是Write Ahead Log (預先日誌持久化)策略,即先寫日誌,再寫入磁碟。

2.Undo日誌


redo log是事務永續性的保證,undo log是事務原子性的保證。在事務中更新資料前置操作其實是要先寫入一個undo log

2.1 如何理解Undo日誌

事務需要保證原子性,也就是事務中的操作要麼全部完成,要麼什麼也不做。但有時候事務執行到一半會出現一些情況,比如:

  • 情況一:事務執行過程中可能遇到各種錯誤,比如MySQL伺服器本身的錯誤作業系統錯誤,甚至是突然斷電導致的錯誤。
  • 情況二:程式設計師可以在事務執行過程中手動輸入ROLLBACK語句結束當前事務的執行。

以上情況出現,我們需要把資料改回原來的樣子,這個過程稱之為回滾,這樣就可以造成一個假象:這個事務看起來什麼都沒做,所以符合原子性要求。

每當我們要對一條記錄做改動時(這裡的改動可以指INSERTDELETEUPDATE),都需要”留一手“ -- 把回滾時所需的東西記下來。比如:

  • 插入一條記錄時,至少要把這條記錄的主鍵值記下來,之後回滾的時候只需要把這個主鍵值對應的記錄刪掉就好了。(對於每個INSERT,InnoDB儲存引擎會完成一個DELETE)。
  • 刪除了一條記錄,至少要把這條記錄中的內容都記下來,這樣之後回滾時再把這些內容組成的記錄插入到表中就好了。(對於每個DELETE,InnoDB儲存引擎會執行一個INSERT)。
  • 修改了一條記錄,至少要把修改這條記錄前的舊值都記錄下來,這樣之後回滾時再把這條記錄更新為舊值就好了。(對於每個UPDATE,InnoDB儲存引擎會執行一個相反的UPDATE,將修改前的行放回去)。

MySQL把這些為了回滾而記錄的這些內容稱之為撤銷日誌或者回滾日誌(即undo log)。注意,由於查詢操作(SELECT)並不會修改任何使用者記錄,所以在查詢操作執行時,並不需要記錄相應的undo日誌。

此外,undo log會產生redo log,也就是undo log的產生會伴隨著redo log的產生,這是因為undo log也需要永續性的保護。

關於這句話,我的理解,在事務的過程中,為了保證資料修改的永續性,會有redo log的生成。為了保證資料能夠實現邏輯上的回滾,注意此處,只是邏輯上的回滾,就好比,在一個事務中a和b,a向b轉了100元,此時事務回滾的話,就是b向a轉回100元。這實際上是兩次update操作,第一次update操作是無法執行撤銷的,只是b向a轉回100元,是MySQL服務讀取undo log日誌,自動執行的,為了保證該語句,執行的永續性,也需要先寫入redo log日誌。但我覺得,undo log的回滾執行,會伴隨著redo log的產生,更好理解。

注意,我只是記錄筆記,方便忘記時檢視,如果筆記不好理解,強烈建議看原影片。我覺得,本章節的知識,確實比較抽象,不是很好理解。

2.2 Undo日誌的作用

  • 作用1:回滾資料

使用者對undo日誌可能有誤解:undo用於將資料庫物理地恢復到執行語句或事務之前的樣子。但事實並非如此。undo是邏輯日誌,只是將資料庫邏輯地恢復到原來的樣子。所有修改都被邏輯地取消了,但是資料結構和頁本身在回滾之後可能大不相同。

這是因為在多使用者併發系統中,可能會有數十,數百甚至數千個併發事務。資料庫的主要任務就是協調對資料記錄的併發訪問。比如,一個事務在修改當前一個頁中某幾條記錄,同時還有別的事務在對同一個頁中另幾條記錄進行修改。因此,不能將一個頁回滾事務開始的樣子,因為這樣會影響其他事務正在進行的工作。

  • 作用2:MVCC

undo的另一個作用是MVCC,即在InnoDB儲存引擎中MVCC的實現是透過undo來完成。當使用者讀取一行記錄時,若該記錄已經被其他事務佔用,當前事務可以透過undo讀取之前的行版本資訊,以此實現非鎖定讀取。MVCC後續章節,會講解。

2.3 undo的儲存結構

1.回滾段與undo頁

InnoDB對undo log的管理採用段的方式,也就是回滾段(rollback segment)。每個回滾段記錄了1024undo log segment,而在每個undo log segment段中進行undo頁的申請。段是MySQL資料儲存結構的一個,行 -> 頁 -> 區 -> 段 -> 表空間,詳細見第七章筆記

  • InnoDB1.1版本之前(不包括1.1版本),只有一個rollback segment,因此支援同時線上的事務限制為1024。雖然對絕大多數的應用來說都已經夠用。
  • 從1.1版本開始InnoDB支援最大128個rollback segment,故其支援同時線上的事務限制提高到了128*1024
mysql> show variables like 'innodb_rollback_segments';
+--------------------------+-------+
| Variable_name            | Value |
+--------------------------+-------+
| innodb_rollback_segments | 128   |
+--------------------------+-------+
1 row in set (0.00 sec)

雖然InnoDB1.1版本支援了128個rollback segment,但是這些rollback segment都儲存於共享表空間ibdata中,從InnoDB1.2版本開始,可透過引數對rollback segment做進一步的設定。這些引數包括:

  • innodb_undo_directory:設定rollback segment檔案所在的路徑。這意味著rollback segment可以存放在共享表空間以外的位置,即可以設定為獨立表空間。該引數的預設值為“”,表示當前InnoDB儲存引擎的目錄。
  • innodb_undo_logs:設定rollback segment的個數,預設值為128。在InnoDB1.2版本中,該引數用來替換之前版本的引數innodb_rollback_segments。注意,在MySQL8.0.25版本下,innodb_undo_logs為空,innodb_rollback_segments正常使用。
  • innodb_undo_tablespaces:設定構成rollback segment檔案的數量,這樣rollback segment可以較為平均地分佈在多個檔案中。設定該引數後,會在路徑innodb_undo_directory看到undo為字首的檔案,該檔案就代表rollback segment檔案。

undo log相關引數一般很少改動。

undo頁的重用

當我們開啟一個事務需要寫undo log的時候,就得先去undo log segment中去找到一個空閒的位置,當有空位的時候,就去申請undo頁,在這個申請到的undo頁中進行undo log的寫入。我們知道mysql預設一頁的大小是16k。

為每一個事務分配一個頁,是非常浪費的(除非你的事務非常長),假設你的應用的TPS(每秒處理的事務數目)為1000,那麼1s就需要1000個頁,大概需要16M的儲存,1分鐘大概需要1G的儲存。如果照這樣下去除非MySQL清理的非常勤快,否則隨著時間的推移,磁碟空間會增長的非常快,而且很多空間都是浪費的。

於是undo頁就被設計的可以重用了,當事務提交時,並不會立刻刪除undo頁。因為重用,所以這個undo頁可能混雜著其他事務的undo log。undo log在commit後,會被放到一個連結串列中,然後判斷undo頁的使用空間是否小於3/4,如果小於3/4的話,則表示當前的undo頁可以被重用,那麼它就不會被回收,其他事務的undo log可以記錄在當前undo頁的後面。由於undo log是離散的,所以清理對應的磁碟空間時,效率不高。

2.回滾段與事務

1.每個事務只會使用一個回滾段,一個回滾段在同一時刻可能會服務於多個事務。

2當一個事務開始的時候,會制定一個回滾段,在事務進行的過程中,當資料被修改時,原始的資料會被複制到回滾段。

3.在回滾段中,事務會不斷填充盤區,直到事務結束或所有的空間被用完。如果當前的盤區不夠用,事務會在段中請求擴充套件下一個盤區,如果所有已分配的盤區都被用完,事務會覆蓋最初的盤區或者在回滾段允許的情況下擴充套件新的盤區來使用。

4.回滾段存在於undo表空闡中,在資料庫中可以存在多個undo表空間,但同一時刻只能使用一個undo表空
間。

mysql> show variables like 'innodb_undo_tablespaces';
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_undo_tablespaces | 2     |
+-------------------------+-------+
1 row in set (0.00 sec)

#undo log的數量,最少為2,undo log的truncate(截斷,類似於刪除)操作有purge(清除)協調執行緒發起。在truncate某個undo log表空間的過程中,保證有一個可用的undo log可用。

5.當事務提交時,InnoDB儲存引擎會做以下兩件事情:

  • 將undo log放入列表中,以供之後的purge操作
  • 判斷undo log所在的頁是否可以重用,若可以分配給下個事務使用
3.回滾段中的資料分類

1.未提交的回滾資料(uncommitted undo information):該資料所關聯的事務並未提交,用於實現讀一致性,所以該資料不能被其他事務的資料覆蓋。

2.已經提交但未過期的回滾資料(committed undo information):該資料關聯的事務已經undo retention引數的保持時間的影響。

3.事務已經提交併過期的資料( expired undo information):事務已經提交,而且資料儲存Eundo retention引數指定的時間,屬於已經過期的資料,當回滾段滿了之後,會優先覆蓋事務已經提交併過期的資料。

事務提交後並不能馬上刪除undo log及undo log所在的頁。這是因為可能還有其他事務需要透過undo log記錄之前的版本。故事提交時將undo log放入一個連結串列中,是否可以最終刪除undo log及undo log所在頁由purge執行緒來判斷。

2.4 undo的型別

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

  • insert undo log

insert undo log是指在insert操作中產生的undo log。因為insert操作的記錄,只對事務本身可見,對其他事務不可見(這是事務隔離性的要求),故該undo log可以在事務提交後直接刪除。不需要purge操作。

我結合上一章筆記,對於本句話的理解:因為MySQL所有的隔離級別中,都避免了髒寫問題,(髒寫,也就是,一個事務讀取到了另一個事務未提交的資料的修改,並對之重新修改)。也就是說,一個事務中,不能讀取到其他事務中最新的內容的修改,只能讀取到undo log暫存中的內容,也就是修改前的資料。而本次事務新增的內容,自然在undo log中,沒有修改前的資料,所以因本次insert操作產生的undo log,在本次事務提交後,也就無需儲存。

  • update undo log

update undo log記錄的是delete和update操作產生的undo log。該undo log可能需要提供MVCC機制,因此不能在事務提交時就進行刪除。提交時放入undo log連結串列,等待purge執行緒進行最後的刪除。這個不是很理解,等學到MVCC章節後,在來回答。

2.5 undo log的生命週期

1.簡要生成過程

以下是undo + redo事務的簡化過程

假設有2個數值,分別為A=1和B=2,然後將A修改為3,B修改為4

1.start transaction;
2.記錄A=1到undo log;
3. update A = 3;
4.記錄A=3 到redo log;
5.記錄B=2到undo log;
6. update B = 4;
7.記錄B = 4到redo log;
8.將redo log重新整理到磁碟
9. commit
  • 在1-8步驟的任意一步系統當機,事務未提交,該事務就不會對磁碟上的資料做任何影響。
  • 如果在8-9之間當機,恢復之後可以選擇回滾,也可以選擇繼續完成事務提交,因為此時redo log已經持久化。
  • 若在9之後系統當機,記憶體對映中變更的資料還來不及刷回磁碟,那麼系統恢復之後,可以根據redo log把資料刷回磁碟。

只有Buffer Pool的流程

有了Redo Log和Undo Log之後:


在更新Buffer Pool中的資料之前,我們需要先將該資料事務開始之前的狀態寫入Undo Log中。假設更新到一半出錯了,我們就可以透過Undo Log來回滾到事務開始前。

2.詳細生成過程

對於InnoDB引擎來說,每個行記錄除了記錄本身的資料之外,還有幾個隱藏的列:

  • DB_ROW_ID:如果沒有為表顯式的定義主鍵,並且表中也沒有定義唯一索引,那麼InnoDB會自動為表新增一個row_id的隱藏列作為主鍵。
  • DB_TRX_ID:每個事務都會分配一個事務ID,當對某條記錄發生變更時,就會將這個事務的事務ID寫入trx_id中。
  • DB_ROLL_PTR:回滾指標,本質上就是指向undo log的指標。

當我們執行INSERT時:

begin;
INSERT INTO user (name) VALUES ("tom");

插入的資料都會生成一條insert undo log,並且資料的回滾指標會指向它。undo log會記錄undo log的序號、插入主鍵的列和值...,那麼在進行rollback的時候,透過主鍵直接把對應的資料刪除即可。

當我們執行UPDATE時:

UPDATE user SET name = 'Sun' WHERE id=1;


這時會把老的記錄寫入新的undo log,讓回滾指標指向新的undo log,它的undo no是1,並且新的undo log會指向老的undo log (undo no=0)。

假設現在執行:

UPDATE user SET id=2 WHERE id=1;

對於更新主鍵的操作,會先把原來的資料deletemark標識開啟,這時並沒有真正的刪除資料,真正的刪除會交給清理執行緒去判斷,然後在後面插入一條新的資料,新的資料也會產生undo log,並且undo log的序號會遞增。可以發現每次對資料的變更都會產生一個undo log,當一條記錄被變更多次時,那麼就會產生多條undo log,undo log記錄的是變更前的日誌,並且每個undo log的序號是遞增的,那麼當要回滾的時候,按照序號依次向前推,就可以找到我們的原始資料了。

3.undo log是如何回滾的

以上面的例子來說,假設執行rollback,那麼對應的流程應該是這樣:

  1. 透過undo no=3的日誌把id=2的資料刪除
  2. 透過undo no=2的日誌把id=1的資料的deletemark還原成0
  3. 透過undo no=1的日誌把id=1的資料的name還原成Tom
  4. 透過undo no=0的日誌把id=1的資料刪除
4.undo log的刪除
  • 針對於insert undo log

因為insert操作的記錄,只對事務本身可見,對其他事務不可見。故該undo log可以在事務提交後直接刪除,不需要進行purge操作。

  • 針對於update undo log

該undo log可能需要提供MVCC機制,因此不能在事務提交時就進行刪除。提交時放入undo log連結串列,等待purge執行緒進行最後的刪除。

補充:
purge執行緒兩個主要作用是:清理undo頁清除page裡面帶有Delete_Bit標識的資料行。在InnoDB中,事務中的Delete操作實際上並不是真正的刪除掉資料行,而是一種Delete Mark操作,在記錄上標識Delete_Bit,而不刪除記錄。是一種"假刪除";只是做了個標記,真正的刪除工作需要後臺purge執行緒去完成。

2.6 小結

undo log是邏輯日誌,對事務回滾時,只是將資料庫邏輯地恢復到原來的樣子。

redo log是物理日誌,記錄的是資料頁的物理變化,undo log不是redo log的逆過程。

只是為了記錄自己的學習歷程,且本人水平有限,不對之處,請指正。

相關文章