1. 程式人生 > >詳細分析MySQL事務日誌(redo log和undo log)

詳細分析MySQL事務日誌(redo log和undo log)

innodb事務日誌包括redo log和undo log。redo log是重做日誌,提供前滾操作.undo log是回滾日誌,提供回滾操作。

undo log和redo log都算是用來恢復的日誌:
1.redo log通常是物理日誌,記錄的是資料頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣,它用來恢復提交後的物理資料頁(恢復資料頁,且只能恢復到最後一次提交的位置)。
2.undo用來回滾行記錄到某個版本。undo log一般是邏輯日誌,根據每行記錄進行記錄。

1.redo log

1.1 redo log和二進位制日誌的區別

注:二進位制日誌相關內容

redo log不是二進位制日誌。雖然二進位制日誌中也記錄了innodb表的很多操作,也能實現重做的功能

但是它們之間有很大區別。

  1. 二進位制日誌是在儲存引擎的上層產生的,與儲存引擎沒有關係,對資料庫進行了修改都會產生二進位制日誌。redo log是innodb層產生的,只記錄該儲存引擎中表的修改。並且二進位制日誌先於redo log被記錄。具體的見後文group commit小結。
  2. 二進位制日誌記錄操作的方法是邏輯性的語句。而redo log是在物理格式上的日誌,它記錄的是資料庫中每個頁的修改
  3. 二進位制日誌只在每次事務提交的時候一次性寫入快取中的日誌"檔案"。而redo log在資料準備修改前寫入快取中的redo log中,然後才對快取中的資料執行修改操作;而且保證在發出事務提交指令時,先向快取中的redo log寫入日誌,寫入完成後才執行提交動作。
  4. 因為二進位制日志只在提交的時候一次性寫入(記錄方式和提交順序有關,且一次提交對應一次記錄)。而redo log中是記錄的物理頁的修改,redo log檔案中同一個事務可能多次記錄,最後一個提交的事務記錄會覆蓋所有未提交的事務記錄。例如事務T1,可能在redo log中記錄了 T1-1,T1-2,T1-3,T1* 共4個操作,其中 T1* 表示最後提交時的日誌記錄,所以對應的資料頁最終狀態是 T1* 對應的操作結果。而且redo log是併發寫入的,不同事務之間的不同版本的記錄會穿插寫入到redo log檔案中,例如可能redo log的記錄方式如下: T1-1,T1-2,T2-1,T2-2,T2*,T1-3
    ,T1*
     。
  5. 事務日誌記錄的是物理頁的情況,它具有冪等性,因此記錄日誌的方式及其簡練。冪等性的意思是多次操作前後狀態是一樣的,例如新插入一行後又刪除該行,前後狀態沒有變化。而二進位制日誌記錄的是所有影響資料的操作,記錄的內容較多。例如插入一行記錄一次,刪除該行又記錄一次。

1.2 redo log的基本概念

redo log包括兩部分:一是記憶體中的日誌緩衝(redo log buffer),該部分日誌是易失性的;二是磁碟上的重做日誌檔案(redo log file),該部分日誌是持久的。

在概念上,innodb通過force log at commit機制實現事務的永續性,即在事務提交的時候,必須先將該事務的所有事務日誌寫入到磁碟上的redo log file和undo log file中進行持久化。

為了確保每次日誌都能寫入到事務日誌檔案中,在每次將log buffer中的日誌寫入日誌檔案的過程中都會呼叫一次作業系統的fsync操作(即fsync()系統呼叫)。因為MariaDB/MySQL是工作在使用者空間的,MariaDB/MySQL的log buffer處於使用者空間的記憶體中。要寫入到磁碟上的log file中(redo:ib_logfileN檔案,undo:share tablespace或.ibd檔案),中間還要經過作業系統核心空間的os buffer,呼叫fsync()的作用就是將OS buffer中的日誌刷到磁碟上的log file中。

也就是說,從redo log buffer寫日誌到磁碟的redo log file中,過程如下: 

在此處需要注意一點,一般所說的log file並不是磁碟上的物理日誌檔案,而是作業系統快取中的log file,官方手冊上的意思也是如此(例如:With a value of 2, the contents of the InnoDB log buffer are written to the log file after each transaction commit and the log file is flushed to disk approximately once per second)。但說實話,這不太好理解,既然都稱為file了,應該已經屬於物理檔案了。所以在本文後續內容中都以os buffer或者file system buffer來表示官方手冊中所說的Log file,然後log file則表示磁碟上的物理日誌檔案,即log file on disk。

MySQL支援使用者自定義在commit時如何將log buffer中的日誌刷log file中。這種控制通過變數 innodb_flush_log_at_trx_commit 的值來決定。該變數有3種值:0、1、2,預設為1。但注意,這個變數只是控制commit動作是否重新整理log buffer到磁碟。

  • 當設定為1的時候,事務每次提交都會將log buffer中的日誌寫入os buffer並呼叫fsync()刷到log file on disk中。這種方式即使系統崩潰也不會丟失任何資料,但是因為每次提交都寫入磁碟,IO的效能較差。
  • 當設定為0的時候,事務提交時不會將log buffer中日誌寫入到os buffer,而是每秒寫入os buffer並呼叫fsync()寫入到log file on disk中。也就是說設定為0時是(大約)每秒重新整理寫入到磁碟中的,當系統崩潰,會丟失1秒鐘的資料。
  • 當設定為2的時候,每次提交都僅寫入到os buffer,然後是每秒呼叫fsync()將os buffer中的日誌寫入到log file on disk。

注意,有一個變數 innodb_flush_log_at_timeout 的值為1秒,該變量表示的是刷日誌的頻率,很多人誤以為是控制 innodb_flush_log_at_trx_commit 值為0和2時的1秒頻率,實際上並非如此。測試時將頻率設定為5和設定為1,當 innodb_flush_log_at_trx_commit 設定為0和2的時候效能基本都是不變的。關於這個頻率是控制什麼的,在後面的"刷日誌到磁碟的規則"中會說。

在主從複製結構中,要保證事務的永續性和一致性,需要對日誌相關變數設定為如下:

  • 如果啟用了二進位制日誌,則設定sync_binlog=1,即每提交一次事務同步寫到磁碟中。
  • 總是設定innodb_flush_log_at_trx_commit=1,即每提交一次事務都寫到磁碟中。

上述兩項變數的設定保證了:每次提交事務都寫入二進位制日誌和事務日誌,並在提交時將它們重新整理到磁碟中。

選擇刷日誌的時間會嚴重影響資料修改時的效能,特別是刷到磁碟的過程。下例就測試了 innodb_flush_log_at_trx_commit 分別為0、1、2時的差距。

#建立測試表
drop table if exists test_flush_log;
create table test_flush_log(id int,name char(50))engine=innodb;

#建立插入指定行數的記錄到測試表中的儲存過程
drop procedure if exists proc;
delimiter $$
create procedure proc(i int)
begin
    declare s int default 1;
    declare c char(50) default repeat('a',50);
    while s<=i do
        start transaction;
        insert into test_flush_log values(null,c);
        commit;
        set s=s+1;
    end while;
end$$
delimiter ;

當前環境下, innodb_flush_log_at_trx_commit 的值為1,即每次提交都刷日誌到磁碟。測試此時插入10W條記錄的時間。

mysql> call proc(100000);
Query OK, 0 rows affected (15.48 sec)

結果是15.48秒。

再測試值為2的時候,即每次提交都重新整理到os buffer,但每秒才刷入磁碟中。

mysql> set @@global.innodb_flush_log_at_trx_commit=2;    
mysql> truncate test_flush_log;

mysql> call proc(100000);
Query OK, 0 rows affected (3.41 sec)

結果插入時間大減,只需3.41秒。

最後測試值為0的時候,即每秒才刷到os buffer和磁碟。

mysql> set @@global.innodb_flush_log_at_trx_commit=0;
mysql> truncate test_flush_log;

mysql> call proc(100000);
Query OK, 0 rows affected (2.10 sec)

結果只有2.10秒。

最後可以發現,其實值為2和0的時候,它們的差距並不太大,但2卻比0要安全的多。它們都是每秒從os buffer刷到磁碟,它們之間的時間差體現在log buffer刷到os buffer上。因為將log buffer中的日誌重新整理到os buffer只是記憶體資料的轉移,並沒有太大的開銷,所以每次提交和每秒刷入差距並不大。可以測試插入更多的資料來比較,以下是插入100W行資料的情況。從結果可見,值為2和0的時候差距並不大,但值為1的效能卻差太多。

儘管設定為0和2可以大幅度提升插入效能,但是在故障的時候可能會丟失1秒鐘資料,這1秒鐘很可能有大量的資料,從上面的測試結果看,100W條記錄也只消耗了20多秒,1秒鐘大約有4W-5W條資料,儘管上述插入的資料簡單,但卻說明了資料丟失的大量性。更好的插入資料的做法是將值設定為1,然後修改儲存過程,將每次迴圈都提交修改為只提交一次這樣既能保證資料的一致性,也能提升效能,修改如下:

drop procedure if exists proc;
delimiter $$
create procedure proc(i int)
begin
    declare s int default 1;
    declare c char(50) default repeat('a',50);
    start transaction;
    while s<=i DO
        insert into test_flush_log values(null,c);
        set s=s+1;
    end while;
    commit;
end$$
delimiter ;

測試值為1時的情況。

mysql> set @@global.innodb_flush_log_at_trx_commit=1;
mysql> truncate test_flush_log;

mysql> call proc(1000000);
Query OK, 0 rows affected (11.26 sec)

1.3 日誌塊(log block)

innodb儲存引擎中,redo log以塊為單位進行儲存的,每個塊佔512位元組,這稱為redo log block。所以不管是log buffer中還是os buffer中以及redo log file on disk中,都是這樣以512位元組的塊儲存的。

每個redo log block由3部分組成:日誌塊頭、日誌塊尾和日誌主體。其中日誌塊頭佔用12位元組,日誌塊尾佔用8位元組,所以每個redo log block的日誌主體部分只有512-12-8=492位元組。

因為redo log記錄的是資料頁的變化,當一個數據頁產生的變化需要使用超過492位元組()的redo log來記錄,那麼就會使用多個redo log block來記錄該資料頁的變化。

日誌塊頭包含4部分:

  • log_block_hdr_no:(4位元組)該日誌塊在redo log buffer中的位置ID。
  • log_block_hdr_data_len:(2位元組)該log block中已記錄的log大小。寫滿該log block時為0x200,表示512位元組。
  • log_block_first_rec_group:(2位元組)該log block中第一個log的開始偏移位置。
  • lock_block_checkpoint_no:(4位元組)寫入檢查點資訊的位置。

關於log block塊頭的第三部分 log_block_first_rec_group ,因為有時候一個數據頁產生的日誌量超出了一個日誌塊,這是需要用多個日誌塊來記錄該頁的相關日誌。例如,某一資料頁產生了552位元組的日誌量,那麼需要佔用兩個日誌塊,第一個日誌塊佔用492位元組,第二個日誌塊需要佔用60個位元組,那麼對於第二個日誌塊來說,它的第一個log的開始位置就是73位元組(60+12)。如果該部分的值和 log_block_hdr_data_len 相等,則說明該log block中沒有新開始的日誌塊,即表示該日誌塊用來延續前一個日誌塊。

日誌尾只有一個部分: log_block_trl_no ,該值和塊頭的 log_block_hdr_no 相等。

上面所說的是一個日誌塊的內容,在redo log buffer或者redo log file on disk中,由很多log block組成。如下圖:

1.4 log group和redo log file

log group表示的是redo log group,一個組內由多個大小完全相同的redo log file組成。組內redo log file的數量由變數 innodb_log_files_group 決定,預設值為2,即兩個redo log file。這個組是一個邏輯的概念,並沒有真正的檔案來表示這是一個組,但是可以通過變數 innodb_log_group_home_dir 來定義組的目錄,redo log file都放在這個目錄下,預設是在datadir下。

mysql> show global variables like "innodb_log%";
+-----------------------------+----------+
| Variable_name               | Value    |
+-----------------------------+----------+
| innodb_log_buffer_size      | 8388608  |
| innodb_log_compressed_pages | ON       |
| innodb_log_file_size        | 50331648 |
| innodb_log_files_in_group   | 2        |
| innodb_log_group_home_dir   | ./       |
+-----------------------------+----------+

[[email protected] data]# ll /mydata/data/ib*
-rw-rw---- 1 mysql mysql 79691776 Mar 30 23:12 /mydata/data/ibdata1
-rw-rw---- 1 mysql mysql 50331648 Mar 30 23:12 /mydata/data/ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Mar 30 23:12 /mydata/data/ib_logfile1

可以看到在預設的資料目錄下,有兩個ib_logfile開頭的檔案,它們就是log group中的redo log file,而且它們的大小完全一致且等於變數 innodb_log_file_size 定義的值。第一個檔案ibdata1是在沒有開啟 innodb_file_per_table 時的共享表空間檔案,對應於開啟 innodb_file_per_table 時的.ibd檔案。

在innodb將log buffer中的redo log block刷到這些log file中時,會以追加寫入的方式迴圈輪訓寫入。即先在第一個log file(即ib_logfile0)的尾部追加寫,直到滿了之後向第二個log file(即ib_logfile1)寫。當第二個log file滿了會清空一部分第一個log file繼續寫入。

由於是將log buffer中的日誌刷到log file,所以在log file中記錄日誌的方式也是log block的方式。

在每個組的第一個redo log file中,前2KB記錄4個特定的部分,從2KB之後才開始記錄log block。除了第一個redo log file中會記錄,log group中的其他log file不會記錄這2KB,但是卻會騰出這2KB的空間。如下:

redo log file的大小對innodb的效能影響非常大,設定的太大,恢復的時候就會時間較長,設定的太小,就會導致在寫redo log的時候迴圈切換redo log file。

1.5 redo log的格式

因為innodb儲存引擎儲存資料的單元是頁(和SQL Server中一樣),所以redo log也是基於頁的格式來記錄的。預設情況下,innodb的頁大小是16KB(由 innodb_page_size 變數控制),一個頁內可以存放非常多的log block(每個512位元組),而log block中記錄的又是資料頁的變化。

其中log block中492位元組的部分是log body,該log body的格式分為4部分:

  • redo_log_type:佔用1個位元組,表示redo log的日誌型別。
  • space:表示表空間的ID,採用壓縮的方式後,佔用的空間可能小於4位元組。
  • page_no:表示頁的偏移量,同樣是壓縮過的。
  • redo_log_body表示每個重做日誌的資料部分,恢復時會呼叫相應的函式進行解析。例如insert語句和delete語句寫入redo log的內容是不一樣的。

如下圖,分別是insert和delete大致的記錄方式。

1.6 日誌刷盤的規則

log buffer中未刷到磁碟的日誌稱為髒日誌(dirty log)。

在上面的說過,預設情況下事務每次提交的時候都會刷事務日誌到磁碟中,這是因為變數 innodb_flush_log_at_trx_commit 的值為1。但是innodb不僅僅只會在有commit動作後才會刷日誌到磁碟,這只是innodb儲存引擎刷日誌的規則之一。

刷日誌到磁碟有以下幾種規則:

1.發出commit動作時。已經說明過,commit發出後是否刷日誌由變數 innodb_flush_log_at_trx_commit 控制。

2.每秒刷一次。這個刷日誌的頻率由變數 innodb_flush_log_at_timeout 值決定,預設是1秒。要注意,這個刷日誌頻率和commit動作無關。

3.當log buffer中已經使用的記憶體超過一半時。

4.當有checkpoint時,checkpoint在一定程度上代表了刷到磁碟時日誌所處的LSN位置。

1.7 資料頁刷盤的規則及checkpoint

記憶體中(buffer pool)未刷到磁碟的資料稱為髒資料(dirty data)。由於資料和日誌都以頁的形式存在,所以髒頁表示髒資料和髒日誌。

上一節介紹了日誌是何時刷到磁碟的,不僅僅是日誌需要刷盤,髒資料頁也一樣需要刷盤。

在innodb中,資料刷盤的規則只有一個:checkpoint。但是觸發checkpoint的情況卻有幾種。不管怎樣,checkpoint觸發後,會將buffer中髒資料頁和髒日誌頁都刷到磁碟。

innodb儲存引擎中checkpoint分為兩種:

  • sharp checkpoint:在重用redo log檔案(例如切換日誌檔案)的時候,將所有已記錄到redo log中對應的髒資料刷到磁碟。
  • fuzzy checkpoint:一次只刷一小部分的日誌到磁碟,而非將所有髒日誌刷盤。有以下幾種情況會觸發該檢查點:
    • master thread checkpoint:由master執行緒控制,每秒或每10秒刷入一定比例的髒頁到磁碟。
    • flush_lru_list checkpoint:從MySQL5.6開始可通過 innodb_page_cleaners 變數指定專門負責髒頁刷盤的page cleaner執行緒的個數,該執行緒的目的是為了保證lru列表有可用的空閒頁。
    • async/sync flush checkpoint:同步刷盤還是非同步刷盤。例如還有非常多的髒頁沒刷到磁碟(非常多是多少,有比例控制),這時候會選擇同步刷到磁碟,但這很少出現;如果髒頁不是很多,可以選擇非同步刷到磁碟,如果髒頁很少,可以暫時不刷髒頁到磁碟
    • dirty page too much checkpoint:髒頁太多時強制觸發檢查點,目的是為了保證快取有足夠的空閒空間。too much的比例由變數 innodb_max_dirty_pages_pct 控制,MySQL 5.6預設的值為75,即當髒頁佔緩衝池的百分之75後,就強制刷一部分髒頁到磁碟。

由於刷髒頁需要一定的時間來完成,所以記錄檢查點的位置是在每次刷盤結束之後才在redo log中標記的。

MySQL停止時是否將髒資料和髒日誌刷入磁碟,由變數innodb_fast_shutdown={ 0|1|2 }控制,預設值為1,即停止時忽略所有flush操作,在下次啟動的時候再flush,實現fast shutdown。

1.8 LSN超詳細分析

LSN稱為日誌的邏輯序列號(log sequence number),在innodb儲存引擎中,lsn佔用8個位元組。LSN的值會隨著日誌的寫入而逐漸增大。

根據LSN,可以獲取到幾個有用的資訊:

1.資料頁的版本資訊。

2.寫入的日誌總量,通過LSN開始號碼和結束號碼可以計算出寫入的日誌量。

3.可知道檢查點的位置。

實際上還可以獲得很多隱式的資訊。

LSN不僅存在於redo log中,還存在於資料頁中,在每個資料頁的頭部,有一個fil_page_lsn記錄了當前頁最終的LSN值是多少。通過資料頁中的LSN值和redo log中的LSN值比較,如果頁中的LSN值小於redo log中LSN值,則表示資料丟失了一部分,這時候可以通過redo log的記錄來恢復到redo log中記錄的LSN值時的狀態。

redo log的lsn資訊可以通過 show engine innodb status 來檢視。MySQL 5.5版本的show結果中只有3條記錄,沒有pages flushed up to。

mysql> show engine innodb stauts
---
LOG
---
Log sequence number 2225502463
Log flushed up to   2225502463
Pages flushed up to 2225502463
Last checkpoint at  2225502463
0 pending log writes, 0 pending chkp writes
3201299 log i/o's done, 0.00 log i/o's/second

其中:

  • log sequence number就是當前的redo log(in buffer)中的lsn;
  • log flushed up to是刷到redo log file on disk中的lsn;
  • pages flushed up to是已經刷到磁碟資料頁上的LSN;
  • last checkpoint at是上一次檢查點所在位置的LSN。

innodb從執行修改語句開始:

(1).首先修改記憶體中的資料頁,並在資料頁中記錄LSN,暫且稱之為data_in_buffer_lsn;

(2).並且在修改資料頁的同時(幾乎是同時)向redo log in buffer中寫入redo log,並記錄下對應的LSN,暫且稱之為redo_log_in_buffer_lsn;

(3).寫完buffer中的日誌後,當觸發了日誌刷盤的幾種規則時,會向redo log file on disk刷入重做日誌,並在該檔案中記下對應的LSN,暫且稱之為redo_log_on_disk_lsn;

(4).資料頁不可能永遠只停留在記憶體中,在某些情況下,會觸發checkpoint來將記憶體中的髒頁(資料髒頁和日誌髒頁)刷到磁碟,所以會在本次checkpoint髒頁刷盤結束時,在redo log中記錄checkpoint的LSN位置,暫且稱之為checkpoint_lsn。

(5).要記錄checkpoint所在位置很快,只需簡單的設定一個標誌即可,但是刷資料頁並不一定很快,例如這一次checkpoint要刷入的資料頁非常多。也就是說要刷入所有的資料頁需要一定的時間來完成,中途刷入的每個資料頁都會記下當前頁所在的LSN,暫且稱之為data_page_on_disk_lsn。

詳細說明如下圖:

上圖中,從上到下的橫線分別代表:時間軸、buffer中資料頁中記錄的LSN(data_in_buffer_lsn)、磁碟中資料頁中記錄的LSN(data_page_on_disk_lsn)、buffer中重做日誌記錄的LSN(redo_log_in_buffer_lsn)、磁碟中重做日誌檔案中記錄的LSN(redo_log_on_disk_lsn)以及檢查點記錄的LSN(checkpoint_lsn)。

假設在最初時(12:0:00)所有的日誌頁和資料頁都完成了刷盤,也記錄好了檢查點的LSN,這時它們的LSN都是完全一致的。

假設此時開啟了一個事務,並立刻執行了一個update操作,執行完成後,buffer中的資料頁和redo log都記錄好了更新後的LSN值,假設為110。這時候如果執行 show engine innodb status 檢視各LSN的值,即圖中①處的位置狀態,結果會是:

log sequence number(110) > log flushed up to(100) = pages flushed up to = last checkpoint at

之後又執行了一個delete語句,LSN增長到150。等到12:00:01時,觸發redo log刷盤的規則(其中有一個規則是 innodb_flush_log_at_timeout 控制的預設日誌刷盤頻率為1秒),這時redo log file on disk中的LSN會更新到和redo log in buffer的LSN一樣,所以都等於150,這時 show engine innodb status ,即圖中②的位置,結果將會是:

log sequence number(150) = log flushed up to > pages flushed up to(100) = last checkpoint at

再之後,執行了一個update語句,快取中的LSN將增長到300,即圖中③的位置。

假設隨後檢查點出現,即圖中④的位置,正如前面所說,檢查點會觸發資料頁和日誌頁刷盤,但需要一定的時間來完成,所以在資料頁刷盤還未完成時,檢查點的LSN還是上一次檢查點的LSN,但此時磁碟上資料頁和日誌頁的LSN已經增長了,即:

log sequence number > log flushed up to 和 pages flushed up to > last checkpoint at

但是log flushed up to和pages flushed up to的大小無法確定,因為日誌刷盤可能快於資料刷盤,也可能等於,還可能是慢於。但是checkpoint機制有保護資料刷盤速度是慢於日誌刷盤的:當資料刷盤速度超過日誌刷盤時,將會暫時停止資料刷盤,等待日誌刷盤進度超過資料刷盤。

等到資料頁和日誌頁刷盤完畢,即到了位置⑤的時候,所有的LSN都等於300。

隨著時間的推移到了12:00:02,即圖中位置⑥,又觸發了日誌刷盤的規則,但此時buffer中的日誌LSN和磁碟中的日誌LSN是一致的,所以不執行日誌刷盤,即此時 show engine innodb status 時各種lsn都相等。

隨後執行了一個insert語句,假設buffer中的LSN增長到了800,即圖中位置⑦。此時各種LSN的大小和位置①時一樣。

隨後執行了提交動作,即位置⑧。預設情況下,提交動作會觸發日誌刷盤,但不會觸發資料刷盤,所以 show engine innodb status 的結果是:

log sequence number = log flushed up to > pages flushed up to = last checkpoint at

最後隨著時間的推移,檢查點再次出現,即圖中位置⑨。但是這次檢查點不會觸發日誌刷盤,因為日誌的LSN在檢查點出現之前已經同步了。假設這次資料刷盤速度極快,快到一瞬間內完成而無法捕捉到狀態的變化,這時 show engine innodb status 的結果將是各種LSN相等。

1.9 innodb的恢復行為

在啟動innodb的時候,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。

因為redo log記錄的是資料頁的物理變化,因此恢復的時候速度比邏輯日誌(如二進位制日誌)要快很多。而且,innodb自身也做了一定程度的優化,讓恢復速度變得更快。

重啟innodb時,checkpoint表示已經完整刷到磁碟上data page上的LSN,因此恢復時僅需要恢復從checkpoint開始的日誌部分。例如,當資料庫在上一次checkpoint的LSN為10000時宕機,且事務是已經提交過的狀態。啟動資料庫時會檢查磁碟中資料頁的LSN,如果資料頁的LSN小於日誌中的LSN,則會從檢查點開始恢復。

還有一種情況,在宕機前正處於checkpoint的刷盤過程,且資料頁的刷盤進度超過了日誌頁的刷盤進度。這時候一宕機,資料頁中記錄的LSN就會大於日誌頁中的LSN,在重啟的恢復過程中會檢查到這一情況,這時超出日誌進度的部分將不會重做,因為這本身就表示已經做過的事情,無需再重做。

另外,事務日誌具有冪等性,所以多次操作得到同一結果的行為在日誌中只記錄一次。而二進位制日誌不具有冪等性,多次操作會全部記錄下來,在恢復的時候會多次執行二進位制日誌中的記錄,速度就慢得多。例如,某記錄中id初始值為2,通過update將值設定為了3,後來又設定成了2,在事務日誌中記錄的將是無變化的頁,根本無需恢復;而二進位制會記錄下兩次update操作,恢復時也將執行這兩次update操作,速度比事務日誌恢復更慢。

1.10 和redo log有關的幾個變數

  • innodb_flush_log_at_trx_commit={0|1|2} # 指定何時將事務日誌刷到磁碟,預設為1。
    • 0表示每秒將"log buffer"同步到"os buffer"且從"os buffer"刷到磁碟日誌檔案中。
    • 1表示每事務提交都將"log buffer"同步到"os buffer"且從"os buffer"刷到磁碟日誌檔案中。
    • 2表示每事務提交都將"log buffer"同步到"os buffer"但每秒才從"os buffer"刷到磁碟日誌檔案中。
  • innodb_log_buffer_size:# log buffer的大小,預設8M
  • innodb_log_file_size:#事務日誌的大小,預設5M
  • innodb_log_files_group =2:# 事務日誌組中的事務日誌檔案個數,預設2個
  • innodb_log_group_home_dir =./:# 事務日誌組路徑,當前目錄表示資料目錄
  • innodb_mirrored_log_groups =1:# 指定事務日誌組的映象組個數,但映象功能好像是強制關閉的,所以只有一個log group。在MySQL5.7中該變數已經移除。

2.undo log

2.1 基本概念

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

在資料修改的時候,不僅記錄了redo,還記錄了相對應的undo,如果因為某些原因導致事務失敗或回滾了,可以藉助該undo進行回滾。

undo log和redo log記錄物理日誌不一樣,它是邏輯日誌。可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。

當執行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容並進行回滾。有時候應用到行版本控制的時候,也是通過undo log來實現的:當讀取的某一行被其他事務鎖定時,它可以從undo log中分析出該行記錄以前的資料是什麼,從而提供該行版本資訊,讓使用者實現非鎖定一致性讀取。

undo log是採用段(segment)的方式來記錄的,每個undo操作在記錄的時候佔用一個undo log segment。

另外,undo log也會產生redo log,因為undo log也要實現永續性保護。

2.2 undo log的儲存方式

innodb儲存引擎對undo的管理採用段的方式。rollback segment稱為回滾段,每個回滾段中有1024個undo log segment。

在以前老版本,只支援1個rollback segment,這樣就只能記錄1024個undo log segment。後來MySQL5.5可以支援128個rollback segment,即支援128*1024個undo操作,還可以通過變數 innodb_undo_logs (5.6版本以前該變數是 innodb_rollback_segments )自定義多少個rollback segment,預設值為128。

undo log預設存放在共享表空間中。

[[email protected] data]# ll /mydata/data/ib*
-rw-rw---- 1 mysql mysql 79691776 Mar 31 01:42 /mydata/data/ibdata1
-rw-rw---- 1 mysql mysql 50331648 Mar 31 01:42 /mydata/data/ib_logfile0
-rw-rw---- 1 mysql mysql 50331648 Mar 31 01:42 /mydata/data/ib_logfile1

如果開啟了 innodb_file_per_table ,將放在每個表的.ibd檔案中。

在MySQL5.6中,undo的存放位置還可以通過變數 innodb_undo_directory 來自定義存放目錄,預設值為"."表示datadir。

預設rollback segment全部寫在一個檔案中,但可以通過設定變數 innodb_undo_tablespaces 平均分配到多少個檔案中。該變數預設值為0,即全部寫入一個表空間檔案。該變數為靜態變數,只能在資料庫示例停止狀態下修改,如寫入配置檔案或啟動時帶上對應引數。但是innodb儲存引擎在啟動過程中提示,不建議修改為非0的值,如下:

2017-03-31 13:16:00 7f665bfab720 InnoDB: Expected to open 3 undo tablespaces but was able
2017-03-31 13:16:00 7f665bfab720 InnoDB: to find only 0 undo tablespaces.
2017-03-31 13:16:00 7f665bfab720 InnoDB: Set the innodb_undo_tablespaces parameter to the
2017-03-31 13:16:00 7f665bfab720 InnoDB: correct value and retry. Suggested value is 0

2.3 和undo log相關的變數

undo相關的變數在MySQL5.6中已經變得很少。如下:它們的意義在上文中已經解釋了。

 mysql> show variables like "%undo%";
+-------------------------+-------+
| Variable_name           | Value |
+-------------------------+-------+
| innodb_undo_directory   | .     |
| innodb_undo_logs        | 128   |
| innodb_undo_tablespaces | 0     |
+-------------------------+-------+

2.4 delete/update操作的內部機制

當事務提交的時候,innodb不會立即刪除undo log,因為後續還可能會用到undo log,如隔離級別為repeatable read時,事務讀取的都是開啟事務時的最新提交行版本,只要該事務不結束,該行版本就不能刪除,即undo log不能刪除。

但是在事務提交的時候,會將該事務對應的undo log放入到刪除列表中,未來通過purge來刪除。並且提交事務時,還會判斷undo log分配的頁是否可以重用,如果可以重用,則會分配給後面來的事務,避免為每個獨立的事務分配獨立的undo log頁而浪費儲存空間和效能。

通過undo log記錄delete和update操作的結果發現:(insert操作無需分析,就是插入行而已)

  • delete操作實際上不會直接刪除,而是將delete物件打上delete flag,標記為刪除,最終的刪除操作是purge執行緒完成的。
  • update分為兩種情況:update的列是否是主鍵列。
    • 如果不是主鍵列,在undo log中直接反向記錄是如何update的。即update是直接進行的。
    • 如果是主鍵列,update分兩部執行:先刪除該行,再插入一行目標行。

3.binlog和事務日誌的先後順序及group commit

如果事務不是隻讀事務,即涉及到了資料的修改,預設情況下會在commit的時候呼叫fsync()將日誌刷到磁碟,保證事務的永續性。

但是一次刷一個事務的日誌效能較低,特別是事務集中在某一時刻時事務量非常大的時候。innodb提供了group commit功能,可以將多個事務的事務日誌通過一次fsync()刷到磁碟中。

因為事務在提交的時候不僅會記錄事務日誌,還會記錄二進位制日誌,但是它們誰先記錄呢?二進位制日誌是MySQL的上層日誌,先於儲存引擎的事務日誌被寫入。

在MySQL5.6以前,當事務提交(即發出commit指令)後,MySQL接收到該訊號進入commit prepare階段;進入prepare階段後,立即寫記憶體中的二進位制日誌,寫完記憶體中的二進位制日誌後就相當於確定了commit操作;然後開始寫記憶體中的事務日誌;最後將二進位制日誌和事務日誌刷盤,它們如何刷盤,分別由變數 sync_binlog 和 innodb_flush_log_at_trx_commit 控制。

但因為要保證二進位制日誌和事務日誌的一致性,在提交後的prepare階段會啟用一個prepare_commit_mutex鎖來保證它們的順序性和一致性。但這樣會導致開啟二進位制日誌後group commmit失效,特別是在主從複製結構中,幾乎都會開啟二進位制日誌。

在MySQL5.6中進行了改進。提交事務時,在儲存引擎層的上一層結構中會將事務按序放入一個佇列,佇列中的第一個事務稱為leader,其他事務稱為follower,leader控制著follower的行為。雖然順序還是一樣先刷二進位制,再刷事務日誌,但是機制完全改變了:刪除了原來的prepare_commit_mutex行為,也能保證即使開啟了二進位制日誌,group commit也是有效的。

MySQL5.6中分為3個步驟:flush階段、sync階段、commit階段。

  • flush階段:向記憶體中寫入每個事務的二進位制日誌。
  • sync階段:將記憶體中的二進位制日誌刷盤。若佇列中有多個事務,那麼僅一次fsync操作就完成了二進位制日誌的刷盤操作。這在MySQL5.6中稱為BLGC(binary log group commit)。
  • commit階段:leader根據順序呼叫儲存引擎層事務的提交,由於innodb本就支援group commit,所以解決了因為鎖 prepare_commit_mutex 而導致的group commit失效問題。

在flush階段寫入二進位制日誌到記憶體中,但是不是寫完就進入sync階段的,而是要等待一定的時間,多積累幾個事務的binlog一起進入sync階段,等待時間由變數 binlog_max_flush_queue_time 決定,預設值為0表示不等待直接進入sync,設定該變數為一個大於0的值的好處是group中的事務多了,效能會好一些,但是這樣會導致事務的響應時間變慢,所以建議不要修改該變數的值,除非事務量非常多並且不斷的在寫入和更新。

進入到sync階段,會將binlog從記憶體中刷入到磁碟,刷入的數量和單獨的二進位制日誌刷盤一樣,由變數 sync_binlog 控制。

當有一組事務在進行commit階段時,其他新事務可以進行flush階段,它們本就不會相互阻塞,所以group commit會不斷生效。當然,group commit的效能和佇列中的事務數量有關,如果每次佇列中只有1個事務,那麼group commit和單獨的commit沒什麼區別,當佇列中事務越來越多時,即提交事務越多越快時,group commit的效果越明顯。

相關推薦

詳細分析MySQL事務日誌(redo logundo log)

innodb事務日誌包括redo log和undo log。redo log是重做日誌,提供前滾操作.undo log是回滾日誌,提供回滾操作。undo log和redo log都算是用來恢復的日誌:1.redo log通常是物理日誌,記錄的是資料頁的物理修改,而不是某一行或

MySQL日誌(二):事務日誌(redo logundo log)

drive datadir sse 詳細分析 mut 通過 註意 默認 into 本文目錄:1.redo log 1.1 redo log和二進制日誌的區別 1.2 redo log的基本概念 1.3 日誌塊(log block) 1.4 log group和redo lo

基於Redo LogUndo LogMySQL崩潰恢復流程

在之前的文章「簡單瞭解InnoDB底層原理」聊了一下MySQL的Buffer Pool。這裡再簡單提一嘴,Buffer Pool是MySQL記憶體結構中十分核心的一個組成,你可以先把它想象成一個黑盒子。 黑盒下的更新資料流程 當我們查詢資料的時候,會先去Buffer Pool中查詢。如果Buffer Pool

Mysql-事務Redo LogUndo Log

一 Undo Log Undo Log是為了實現事務的原子性,在MySQL資料庫InnoDB儲存引擎中,還用Undo Log來實現多版本併發控制(簡稱:MVCC)。 1   事務的原子性(Atomicity) 事務中的所有操作,要麼全部完成,要麼不做任何操作,不能只做部

深入理解MySQL系列之redo logundo logbinlog

#### 事務的實現 redo log保證事務的永續性,undo log用來幫助事務回滾及MVCC的功能。 #### InnoDB儲存引擎體系結構 ![](https://img2020.cnblogs.com/blog/2211828/202012/2211828-20201222093735507-1

【20180417】ELK日誌管理之filebeat收集分析mysql日誌

filebeat slow log pipeline slowlog 環境版本 filebeat: 6.2.3mysql: 5.6.38 錯誤信息 { "_index": "mysql-slow-2018.04.17", "_type": "doc", "_id": "AWLRiD

Mysql 事務日誌(Ib_logfile)

內存 並不是 內存結構 關閉數據庫 flush 使用 有一個 漏鬥 對齊 mysql的innodb中事務日誌ib_logfile(0/1)概念:事務日誌或稱redo日誌,在mysql中默認以ib_logfile0,ib_logfile1名稱存在,可以手工修改參數,調節開啟幾

八大常用排序演算法詳細分析 包括複雜度,原理實現

1. 氣泡排序 1.1 演算法原理: S1:從待排序序列的起始位置開始,從前往後依次比較各個位置和其後一位置的大小並執行S2。  S2:如果當前位置的值大於其後一位置的值,就把他倆的值交換(完成一次全序列比較後,序列最後位置的值即此序列最大值,所以其不需要再參與冒泡)。  S3:將序列的最

bin logredo logundo logMVVC

cond 存在 mvc 一個 -c 能夠 開始 elastic 復制。 logs innodb事務日誌包括redo log和undo log。redo log是重做日誌,提供前滾操作,undo log是回滾日誌,提供回滾操作。 undo log不是redo log的逆向過程

MySQL事務的隔離級別ACID

導致 持久 完成 數據 實現 容易 規則 除了 一份 在MySQL中隔離性有4種級別, read-uncommitted (讀未提交 ;會產生臟讀,不可重復讀,幻讀)在該隔離級別,所有事務都可以看到其他未提交事務的執行結果。本隔離級別很少用於實際應用,因為它的性能也不比

實戰分析事務的隔離級別傳播屬性

什麼是事務? 要麼全部都要執行,要麼就都不執行。 事務所具有的四種特性 原子性,一致性,隔離性,永續性 原子性  個人理解,就是事務執行不可分割,要麼全部完成,要麼全部拉倒不幹。 一致性  關於一致性這個概念我們來舉個例子說明吧,假設張三給李四轉了100元,那麼需要先從張三那邊扣除100,

超乾貨!為了讓你徹底弄懂MySQL事務日誌,我通宵肝出了這份圖解!

還記得剛上研究生的時候,導師常掛在嘴邊的一句話,“科研的基礎不過就是資料而已。”如今看來,無論是人文社科,還是自然科學,或許都可在一定程度上看作是資料的科學。 倘若剝開研究領域的外衣,將人的操作抽象出來,那麼科研的過程大概就是根據資料流動探索其中的未知資訊吧。當然科學研究的範疇涵蓋甚廣,也不是一兩句話能夠拎

Apache下access.logerror.log檔案太大的處理方法

第一步:停止Apache服務的所有程序,刪除 Apache2/logs/目錄下的 error.log、access.log檔案 第二步:開啟 Apache 的 httpd.conf配置檔案並找到下面兩條配置 ErrorLog logs/error.log CustomLog logs/access.log

binlog,redo logundo log區別

1. binlog是MySQL Server層記錄的日誌, redo log是InnoDB儲存引擎層的日誌。 兩者都是記錄了某些操作的日誌(不是所有)自然有些重複(但兩者記錄的格式不同)。 2. 選擇binlog日誌作為replication我想主要原因是MySQL的特點

MySQL 日誌系統之 redo log binlog

之前我們瞭解了一條查詢語句的執行流程,並介紹了執行過程中涉及的處理模組。一條查詢語句的執行過程一般是經過聯結器、分析器、優化器、執行器等功能模組,最後到達儲存引擎。 那麼,一條 SQL 更新語句的執行流程又是怎樣的呢? 首先我們建立一個表 user_info,主鍵為 id,建立語句如下: CREATE TAB

Mysql裡的 undo log redo log

轉載自:http://doc.okbase.net/xinysu/archive/259593.html 1 undo 1.1 undo是啥 undo日誌用於存放資料修改被修改前的值,假設修改 tba 表中 id=2的行資料,把Name='B' 修改為Name = 'B2' ,那麼u

MySQL中的重做日誌redo log),回滾日誌undo log),以及二進位制日誌(binlog)的簡單總結

MySQL中有六種日誌檔案, 分別是:重做日誌(redo log)、回滾日誌(undo log)、二進位制日誌(binlog)、錯誤日誌(errorlog)、慢查詢日誌(slow query log)、一般查詢日誌(general log),中繼日誌(relay log)。 其中重做日誌和回滾日誌與

MySQL的萬字總結(快取,索引,Explain,事務redo日誌等)

hello,小夥伴們,好久不見,MySQL系列停更了差不多兩個月了,也有小夥伴問我為啥不更了呢?其實我去看了MySQL的全集,準備憋個大招,更新篇長文(我不會告訴你是因為我懶的)。 好了,話不多說,直接開始吧。這篇文章將從查詢快取,索引,優化器,explain,redo日誌,undo日誌,事務隔離級別,鎖等方

實戰:MySQL Sending data導致查詢很慢的問題詳細分析(轉)

sql 格式 一段 ace 研究 軟件測試 tar 遊戲 很好 這兩天幫忙定位一個MySQL查詢很慢的問題,定位過程綜合各種方法、理論、工具,很有代表性,分享給大家作為新年禮物:) 【問題現象】 使用sphinx支持倒排索引,但sphinx從mysql查詢源數據的

補碼詳細分析匯編下的使用

等號 反碼 記憶 進行 結果 line 絕對值 相加 -128 原碼,反碼,補碼 考慮範圍:二進制8位整數,[-128,127] ()反:二進制數中的所有的0變為1;所有的1變為0 這裏的01串默認為二進制 I.原碼 把數的絕對值寫成二進制的形式(7位),其中-128只