日本黄色一级经典视频|伊人久久精品视频|亚洲黄色色周成人视频九九九|av免费网址黄色小短片|黄色Av无码亚洲成年人|亚洲1区2区3区无码|真人黄片免费观看|无码一级小说欧美日免费三级|日韩中文字幕91在线看|精品久久久无码中文字幕边打电话

當前位置:首頁 > > 架構師社區(qū)
[導讀]innodb事務日志包括redo log和undo log。redo log是重做日志,提供前滾操作,undo log是回滾日志,提供回滾操作。undo log不是redo log的逆向過程,其實它們都算是用來恢復的日志。

來源:https://blog.csdn.net/demonson/article/details/104369733

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

undo log不是redo log的逆向過程,其實它們都算是用來恢復的日志:

1.redo log通常是物理日志,記錄的是數(shù)據(jù)頁的物理修改,而不是某一行或某幾行修改成怎樣怎樣,它用來恢復提交后的物理數(shù)據(jù)頁(恢復數(shù)據(jù)頁,且只能恢復到最后一次提交的位置)。

2.undo用來回滾行記錄到某個版本。undo log一般是邏輯日志,根據(jù)每行記錄進行記錄。


1.redo log

1.1 redo log和二進制日志的區(qū)別

二進制日志相關內容,參考:MariaDB/MySQL的二進制日志。
redo log不是二進制日志。雖然二進制日志中也記錄了innodb表的很多操作, 也能實現(xiàn)重做的功能,但是它們之間有很大區(qū)別。
  1. 二進制日志是在 存儲引擎的上層產生的,不管是什么存儲引擎,對數(shù)據(jù)庫進行了修改都會產生二進制日志。而redo log是innodb層產生的,只記錄該存儲引擎中表的修改。 并且二進制日志先于redo log被記錄。具體的見后文group commit小結。
  2. 二進制日志記錄操作的方法是邏輯性的語句。即便它是基于行格式的記錄方式,其本質也還是邏輯的SQL設置,如該行記錄的每列的值是多少。而redo log是在物理格式上的日志,它記錄的是數(shù)據(jù)庫中每個頁的修改。
  3. 二進制日志只在每次事務提交的時候一次性寫入緩存中的日志"文件"(對于非事務表的操作,則是每次執(zhí)行語句成功后就直接寫入)。而redo log在數(shù)據(jù)準備修改前寫入緩存中的redo log中,然后才對緩存中的數(shù)據(jù)執(zhí)行修改操作;而且保證在發(fā)出事務提交指令時,先向緩存中的redo log寫入日志,寫入完成后才執(zhí)行提交動作。
  4. 因為二進制日志只在提交的時候一次性寫入,所以二進制日志中的記錄方式和提交順序有關,且一次提交對應一次記錄。而redo log中是記錄的物理頁的修改,redo log文件中同一個事務可能多次記錄,最后一個提交的事務記錄會覆蓋所有未提交的事務記錄。例如事務T1,可能在redo log中記錄了 T1-1,T1-2,T1-3,T1*?共4個操作,其中 T1*?表示最后提交時的日志記錄,所以對應的數(shù)據(jù)頁最終狀態(tài)是 T1*?對應的操作結果。而且redo log是并發(fā)寫入的,不同事務之間的不同版本的記錄會穿插寫入到redo log文件中,例如可能redo log的記錄方式如下:T1-1,T1-2,T2-1,T2-2,T2*,T1-3,T1*?。
  5. 事務日志記錄的是物理頁的情況,它具有冪等性,因此記錄日志的方式極其簡練。冪等性的意思是多次操作前后狀態(tài)是一樣的,例如新插入一行后又刪除該行,前后狀態(tài)沒有變化。而二進制日志記錄的是所有影響數(shù)據(jù)的操作,記錄的內容較多。例如插入一行記錄一次,刪除該行又記錄一次。

1.2 redo log的基本概念

redo log包括兩部分:一是內存中的日志緩沖(redo log buffer),該部分日志是易失性的;二是磁盤上的重做日志文件(redo log file),該部分日志是持久的。
在概念上,innodb通過 force log at commit機制實現(xiàn)事務的持久性,即在事務提交的時候,必須先將該事務的所有事務日志寫入到磁盤上的redo log file和undo log file中進行持久化。
為了確保每次日志都能寫入到事務日志文件中,在每次將log buffer中的日志寫入日志文件的過程中都會調用一次操作系統(tǒng)的fsync操作(即fsync()系統(tǒng)調用)。因為MariaDB/MySQL是工作在用戶空間的,MariaDB/MySQL的log buffer處于用戶空間的內存中。要寫入到磁盤上的log file中(redo:ib_logfileN文件,undo:share tablespace或.ibd文件),中間還要經過操作系統(tǒng)內核空間的os buffer,調用fsync()的作用就是將OS buffer中的日志刷到磁盤上的log file中。
也就是說,從redo log buffer寫日志到磁盤的redo log file中,過程如下:?
精講MySQL事務日志:redo log 和 undo log
在此處需要注意一點,一般所說的log file并不是磁盤上的物理日志文件,而是操作系統(tǒng)緩存中的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了,應該已經屬于物理文件了。所以在本文后續(xù)內容中都以os buffer或者file system buffer來表示官方手冊中所說的Log file,然后log file則表示磁盤上的物理日志文件,即log file on disk。
另外,之所以要經過一層os buffer,是因為open日志文件的時候,open沒有使用O_DIRECT標志位,該標志位意味著繞過操作系統(tǒng)層的os buffer,IO直寫到底層存儲設備。不使用該標志位意味著將日志進行緩沖,緩沖到了一定容量,或者顯式fsync()才會將緩沖中的刷到存儲設備。使用該標志位意味著每次都要發(fā)起系統(tǒng)調用。比如寫abcde,不使用o_direct將只發(fā)起一次系統(tǒng)調用,使用o_object將發(fā)起5次系統(tǒng)調用。
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中。這種方式即使系統(tǒng)崩潰也不會丟失任何數(shù)據(jù),但是因為每次提交都寫入磁盤,IO的性能較差。
  • 當設置為0的時候,事務提交時不會將log buffer中日志寫入到os buffer,而是每秒寫入os buffer并調用fsync()寫入到log file on disk中。也就是說設置為0時是(大約)每秒刷新寫入到磁盤中的,當系統(tǒng)崩潰,會丟失1秒鐘的數(shù)據(jù)。
  • 當設置為2的時候,每次提交都僅寫入到os buffer,然后是每秒調用fsync()將os buffer中的日志寫入到log file on disk。
精講MySQL事務日志:redo log 和 undo log
注意,有一個變量 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的時候性能基本都是不變的。關于這個頻率是控制什么的,在后面的"刷日志到磁盤的規(guī)則"中會說。
在主從復制結構中,要保證事務的持久性和一致性,需要對日志相關變量設置為如下:
  • 如果啟用了二進制日志,則設置sync_binlog=1,即每提交一次事務同步寫到磁盤中。
  • 總是設置innodb_flush_log_at_trx_commit=1,即每提交一次事務都寫到磁盤中。
上述兩項變量的設置保證了:每次提交事務都寫入二進制日志和事務日志,并在提交時將它們刷新到磁盤中。
選擇刷日志的時間會嚴重影響數(shù)據(jù)修改時的性能,特別是刷到磁盤的過程。下例就測試了 innodb_flush_log_at_trx_commit 分別為0、1、2時的差距。
   
#創(chuàng)建測試表
    
drop table if exists test_flush_log; create table test_flush_log(id int,name char(50))engine=innodb;
   

#創(chuàng)建插入指定行數(shù)的記錄到測試表中的存儲過程
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 ;

當前環(huán)境下, 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秒。
最后可以發(fā)現(xiàn),其實值為2和0的時候,它們的差距并不太大,但2卻比0要安全的多。它們都是每秒從os buffer刷到磁盤,它們之間的時間差體現(xiàn)在log buffer刷到os buffer上。因為將log buffer中的日志刷新到os buffer只是內存數(shù)據(jù)的轉移,并沒有太大的開銷,所以每次提交和每秒刷入差距并不大??梢詼y試插入更多的數(shù)據(jù)來比較,以下是插入100W行數(shù)據(jù)的情況。從結果可見,值為2和0的時候差距并不大,但值為1的性能卻差太多。
精講MySQL事務日志:redo log 和 undo log
盡管設置為0和2可以大幅度提升插入性能,但是在故障的時候可能會丟失1秒鐘數(shù)據(jù),這1秒鐘很可能有大量的數(shù)據(jù),從上面的測試結果看,100W條記錄也只消耗了20多秒,1秒鐘大約有4W-5W條數(shù)據(jù),盡管上述插入的數(shù)據(jù)簡單,但卻說明了數(shù)據(jù)丟失的大量性。 更好的插入數(shù)據(jù)的做法是將值設置為1,然后修改存儲過程,將每次循環(huán)都提交修改為只提交一次,這樣既能保證數(shù)據(jù)的一致性,也能提升性能,修改如下:
    
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字節(jié),這稱為redo log block。所以不管是log buffer中還是os buffer中以及redo log file on disk中,都是這樣以512字節(jié)的塊存儲的。
每個redo log block由3部分組成: 日志塊頭、日志塊尾和日志主體。其中日志塊頭占用12字節(jié),日志塊尾占用8字節(jié),所以每個redo log block的日志主體部分只有512-12-8=492字節(jié)。
精講MySQL事務日志:redo log 和 undo log
因為redo log記錄的是數(shù)據(jù)頁的變化,當一個數(shù)據(jù)頁產生的變化需要使用超過492字節(jié)()的redo log來記錄,那么就會使用多個redo log block來記錄該數(shù)據(jù)頁的變化。
日志塊頭包含4部分:
  • log_block_hdr_no:(4字節(jié))該日志塊在redo log buffer中的位置ID。
  • log_block_hdr_data_len:(2字節(jié))該log block中已記錄的log大小。寫滿該log block時為0x200,表示512字節(jié)。
  • log_block_first_rec_group:(2字節(jié))該log block中第一個log的開始偏移位置。
  • lock_block_checkpoint_no:(4字節(jié))寫入檢查點信息的位置。
關于log block塊頭的第三部分 log_block_first_rec_group ,因為有時候一個數(shù)據(jù)頁產生的日志量超出了一個日志塊,這是需要用多個日志塊來記錄該頁的相關日志。例如,某一數(shù)據(jù)頁產生了552字節(jié)的日志量,那么需要占用兩個日志塊,第一個日志塊占用492字節(jié),第二個日志塊需要占用60個字節(jié),那么對于第二個日志塊來說,它的第一個log的開始位置就是73字節(jié)(60+12)。如果該部分的值和 log_block_hdr_data_len 相等,則說明該log block中沒有新開始的日志塊,即表示該日志塊用來延續(xù)前一個日志塊。
日志尾只有一個部分:log_block_trl_no ,該值和塊頭的 log_block_hdr_no 相等。
上面所說的是一個日志塊的內容,在redo log buffer或者redo log file on disk中,由很多l(xiāng)og block組成。如下圖:
精講MySQL事務日志:redo log 和 undo log

1.4 log group和redo log file

log group表示的是redo log group,一個組內由多個大小完全相同的redo log file組成。組內redo log file的數(shù)量由變量 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 | ./ |
+-----------------------------+----------+

[root@xuexi 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
可以看到在默認的數(shù)據(jù)目錄下,有兩個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中時,會以追加寫入的方式循環(huán)輪訓寫入。即先在第一個log file(即ib_logfile0)的尾部追加寫,直到滿了之后向第二個log file(即ib_logfile1)寫。當?shù)诙€log file滿了會清空一部分第一個log file繼續(xù)寫入。
由于是將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的空間。如下:
精講MySQL事務日志:redo log 和 undo log
redo log file的大小對innodb的性能影響非常大,設置的太大,恢復的時候就會時間較長,設置的太小,就會導致在寫redo log的時候循環(huán)切換redo log file。

1.5 redo log的格式

因為innodb存儲引擎存儲數(shù)據(jù)的單元是頁(和SQL Server中一樣),所以redo log也是基于頁的格式來記錄的。默認情況下,innodb的頁大小是16KB(由 innodb_page_size 變量控制),一個頁內可以存放非常多的log block(每個512字節(jié)),而log block中記錄的又是數(shù)據(jù)頁的變化。
其中l(wèi)og block中492字節(jié)的部分是log body,該log body的格式分為4部分:
  • redo_log_type:占用1個字節(jié),表示redo log的日志類型。
  • space:表示表空間的ID,采用壓縮的方式后,占用的空間可能小于4字節(jié)。
  • page_no:表示頁的偏移量,同樣是壓縮過的。
  • redo_log_body表示每個重做日志的數(shù)據(jù)部分,恢復時會調用相應的函數(shù)進行解析。例如insert語句和delete語句寫入redo log的內容是不一樣的。
如下圖,分別是insert和delete大致的記錄方式。
精講MySQL事務日志:redo log 和 undo log

1.6 日志刷盤的規(guī)則

log buffer中未刷到磁盤的日志稱為臟日志(dirty log)。
在上面的說過,默認情況下事務每次提交的時候都會刷事務日志到磁盤中,這是因為變量 innodb_flush_log_at_trx_commit 的值為1。但是innodb不僅僅只會在有commit動作后才會刷日志到磁盤,這只是innodb存儲引擎刷日志的規(guī)則之一。
刷日志到磁盤有以下幾種規(guī)則:
1.發(fā)出commit動作時。已經說明過,commit發(fā)出后是否刷日志由變量 innodb_flush_log_at_trx_commit 控制。
2.每秒刷一次。這個刷日志的頻率由變量 innodb_flush_log_at_timeout 值決定,默認是1秒。要注意,這個刷日志頻率和commit動作無關。
3.當log buffer中已經使用的內存超過一半時。
4.當有checkpoint時,checkpoint在一定程度上代表了刷到磁盤時日志所處的LSN位置。

1.7 數(shù)據(jù)頁刷盤的規(guī)則及checkpoint

內存中(buffer pool)未刷到磁盤的數(shù)據(jù)稱為臟數(shù)據(jù)(dirty data)。由于數(shù)據(jù)和日志都以頁的形式存在,所以臟頁表示臟數(shù)據(jù)和臟日志。
上一節(jié)介紹了日志是何時刷到磁盤的,不僅僅是日志需要刷盤,臟數(shù)據(jù)頁也一樣需要刷盤。
在innodb中,數(shù)據(jù)刷盤的規(guī)則只有一個:checkpoint。但是觸發(fā)checkpoint的情況卻有幾種。 不管怎樣,checkpoint觸發(fā)后,會將buffer中臟數(shù)據(jù)頁和臟日志頁都刷到磁盤。
innodb存儲引擎中checkpoint分為兩種:
  • sharp checkpoint:在重用redo log文件(例如切換日志文件)的時候,將所有已記錄到redo log中對應的臟數(shù)據(jù)刷到磁盤。
  • fuzzy checkpoint:一次只刷一小部分的日志到磁盤,而非將所有臟日志刷盤。有以下幾種情況會觸發(fā)該檢查點:
    • master thread checkpoint:由master線程控制, 每秒或每10秒刷入一定比例的臟頁到磁盤。
    • flush_lru_list checkpoint:從MySQL5.6開始可通過 innodb_page_cleaners 變量指定專門負責臟頁刷盤的page cleaner線程的個數(shù),該線程的目的是為了保證lru列表有可用的空閑頁。
    • async/sync flush checkpoint:同步刷盤還是異步刷盤。例如還有非常多的臟頁沒刷到磁盤(非常多是多少,有比例控制),這時候會選擇同步刷到磁盤,但這很少出現(xiàn);如果臟頁不是很多,可以選擇異步刷到磁盤,如果臟頁很少,可以暫時不刷臟頁到磁盤
    • dirty page too much checkpoint:臟頁太多時強制觸發(fā)檢查點,目的是為了保證緩存有足夠的空閑空間。too much的比例由變量 innodb_max_dirty_pages_pct 控制,MySQL 5.6默認的值為75,即當臟頁占緩沖池的百分之75后,就強制刷一部分臟頁到磁盤。
由于刷臟頁需要一定的時間來完成,所以記錄檢查點的位置是在每次刷盤結束之后才在redo log中標記的。
MySQL停止時是否將臟數(shù)據(jù)和臟日志刷入磁盤,由變量innodb_fast_shutdown={ 0|1|2 }控制,默認值為1,即停止時只做一部分purge,忽略大多數(shù)flush操作(但至少會刷日志),在下次啟動的時候再flush剩余的內容,實現(xiàn)fast shutdown。

1.8 LSN超詳細分析

LSN稱為日志的邏輯序列號(log sequence number),在innodb存儲引擎中,lsn占用8個字節(jié)。LSN的值會隨著日志的寫入而逐漸增大。
根據(jù)LSN,可以獲取到幾個有用的信息:
1.數(shù)據(jù)頁的版本信息。
2.寫入的日志總量,通過LSN開始號碼和結束號碼可以計算出寫入的日志量。
3.可知道檢查點的位置。
實際上還可以獲得很多隱式的信息。
LSN不僅存在于redo log中,還存在于數(shù)據(jù)頁中,在每個數(shù)據(jù)頁的頭部,有一個 fil_page_lsn記錄了當前頁最終的LSN值是多少。通過數(shù)據(jù)頁中的LSN值和redo log中的LSN值比較,如果頁中的LSN值小于redo log中LSN值,則表示數(shù)據(jù)丟失了一部分,這時候可以通過redo log的記錄來恢復到redo log中記錄的LSN值時的狀態(tài)。
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是已經刷到磁盤數(shù)據(jù)頁上的LSN;
  • last checkpoint at是上一次檢查點所在位置的LSN。
innodb從執(zhí)行修改語句開始:
(1).首先修改內存中的數(shù)據(jù)頁,并在數(shù)據(jù)頁中記錄LSN,暫且稱之為data_in_buffer_lsn;
(2).并且在修改數(shù)據(jù)頁的同時(幾乎是同時)向redo log in buffer中寫入redo log,并記錄下對應的LSN,暫且稱之為redo_log_in_buffer_lsn;
(3).寫完buffer中的日志后,當觸發(fā)了日志刷盤的幾種規(guī)則時,會向redo log file on disk刷入重做日志,并在該文件中記下對應的LSN,暫且稱之為redo_log_on_disk_lsn;
(4).數(shù)據(jù)頁不可能永遠只停留在內存中,在某些情況下,會觸發(fā)checkpoint來將內存中的臟頁(數(shù)據(jù)臟頁和日志臟頁)刷到磁盤,所以會在本次checkpoint臟頁刷盤結束時,在redo log中記錄checkpoint的LSN位置,暫且稱之為checkpoint_lsn。
(5).要記錄checkpoint所在位置很快,只需簡單的設置一個標志即可,但是刷數(shù)據(jù)頁并不一定很快,例如這一次checkpoint要刷入的數(shù)據(jù)頁非常多。也就是說要刷入所有的數(shù)據(jù)頁需要一定的時間來完成,中途刷入的每個數(shù)據(jù)頁都會記下當前頁所在的LSN,暫且稱之為data_page_on_disk_lsn。
詳細說明如下圖:
精講MySQL事務日志:redo log 和 undo log
上圖中,從上到下的橫線分別代表:時間軸、buffer中數(shù)據(jù)頁中記錄的LSN(data_in_buffer_lsn)、磁盤中數(shù)據(jù)頁中記錄的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)所有的日志頁和數(shù)據(jù)頁都完成了刷盤,也記錄好了檢查點的LSN,這時它們的LSN都是完全一致的。
假設此時開啟了一個事務,并立刻執(zhí)行了一個update操作,執(zhí)行完成后,buffer中的數(shù)據(jù)頁和redo log都記錄好了更新后的LSN值,假設為110。這時候如果執(zhí)行 show engine innodb status 查看各LSN的值,即圖中①處的位置狀態(tài),結果會是:
   
log sequence number(110) > log flushed up to(100) = pages flushed up to = last checkpoint at
之后又執(zhí)行了一個delete語句,LSN增長到150。等到12:00:01時,觸發(fā)redo log刷盤的規(guī)則(其中有一個規(guī)則是 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
再之后,執(zhí)行了一個update語句,緩存中的LSN將增長到300,即圖中③的位置。
假設隨后檢查點出現(xiàn),即圖中④的位置,正如前面所說,檢查點會觸發(fā)數(shù)據(jù)頁和日志頁刷盤,但需要一定的時間來完成,所以在數(shù)據(jù)頁刷盤還未完成時,檢查點的LSN還是上一次檢查點的LSN,但此時磁盤上數(shù)據(jù)頁和日志頁的LSN已經增長了,即:
    
log sequence number > log flushed up to 和 pages flushed up to > last checkpoint at
但是log flushed up to和pages flushed up to的大小無法確定,因為日志刷盤可能快于數(shù)據(jù)刷盤,也可能等于,還可能是慢于。但是checkpoint機制有保護數(shù)據(jù)刷盤速度是慢于日志刷盤的:當數(shù)據(jù)刷盤速度超過日志刷盤時,將會暫時停止數(shù)據(jù)刷盤,等待日志刷盤進度超過數(shù)據(jù)刷盤。
等到數(shù)據(jù)頁和日志頁刷盤完畢,即到了位置⑤的時候,所有的LSN都等于300。
隨著時間的推移到了12:00:02,即圖中位置⑥,又觸發(fā)了日志刷盤的規(guī)則,但此時buffer中的日志LSN和磁盤中的日志LSN是一致的,所以不執(zhí)行日志刷盤,即此時 show engine innodb status 時各種lsn都相等。
隨后執(zhí)行了一個insert語句,假設buffer中的LSN增長到了800,即圖中位置⑦。此時各種LSN的大小和位置①時一樣。
隨后執(zhí)行了提交動作,即位置⑧。默認情況下,提交動作會觸發(fā)日志刷盤,但不會觸發(fā)數(shù)據(jù)刷盤,所以 show engine innodb status 的結果是:
    
log sequence number = log flushed up to > pages flushed up to = last checkpoint at
最后隨著時間的推移,檢查點再次出現(xiàn),即圖中位置⑨。但是這次檢查點不會觸發(fā)日志刷盤,因為日志的LSN在檢查點出現(xiàn)之前已經同步了。假設這次數(shù)據(jù)刷盤速度極快,快到一瞬間內完成而無法捕捉到狀態(tài)的變化,這時 show engine innodb status 的結果將是各種LSN相等。

1.9 innodb的恢復行為

在啟動innodb的時候,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。
因為redo log記錄的是數(shù)據(jù)頁的物理變化,因此恢復的時候速度比邏輯日志(如二進制日志)要快很多。而且,innodb自身也做了一定程度的優(yōu)化,讓恢復速度變得更快。
重啟innodb時,checkpoint表示已經完整刷到磁盤上data page上的LSN,因此恢復時僅需要恢復從checkpoint開始的日志部分。例如,當數(shù)據(jù)庫在上一次checkpoint的LSN為10000時宕機,且事務是已經提交過的狀態(tài)。啟動數(shù)據(jù)庫時會檢查磁盤中數(shù)據(jù)頁的LSN,如果數(shù)據(jù)頁的LSN小于日志中的LSN,則會從檢查點開始恢復。
還有一種情況,在宕機前正處于checkpoint的刷盤過程,且數(shù)據(jù)頁的刷盤進度超過了日志頁的刷盤進度。這時候一宕機,數(shù)據(jù)頁中記錄的LSN就會大于日志頁中的LSN,在重啟的恢復過程中會檢查到這一情況,這時超出日志進度的部分將不會重做,因為這本身就表示已經做過的事情,無需再重做。
另外,事務日志具有冪等性,所以多次操作得到同一結果的行為在日志中只記錄一次。而二進制日志不具有冪等性,多次操作會全部記錄下來,在恢復的時候會多次執(zhí)行二進制日志中的記錄,速度就慢得多。例如,某記錄中id初始值為2,通過update將值設置為了3,后來又設置成了2,在事務日志中記錄的將是無變化的頁,根本無需恢復;而二進制會記錄下兩次update操作,恢復時也將執(zhí)行這兩次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:# 事務日志組中的事務日志文件個數(shù),默認2個
  • innodb_log_group_home_dir =./:# 事務日志組路徑,當前目錄表示數(shù)據(jù)目錄
  • innodb_mirrored_log_groups =1:# 指定事務日志組的鏡像組個數(shù),但鏡像功能好像是強制關閉的,所以只有一個log group。在MySQL5.7中該變量已經移除。

2.undo log


2.1 基本概念

undo log有兩個作用:提供回滾和多個行版本控制(MVCC)。
在數(shù)據(jù)修改的時候,不僅記錄了redo,還記錄了相對應的undo,如果因為某些原因導致事務失敗或回滾了,可以借助該undo進行回滾。
undo log和redo log記錄物理日志不一樣,它是邏輯日志。 可以認為當delete一條記錄時,undo log中會記錄一條對應的insert記錄,反之亦然,當update一條記錄時,它記錄一條對應相反的update記錄。
當執(zhí)行rollback時,就可以從undo log中的邏輯記錄讀取到相應的內容并進行回滾。有時候應用到行版本控制的時候,也是通過undo log來實現(xiàn)的:當讀取的某一行被其他事務鎖定時,它可以從undo log中分析出該行記錄以前的數(shù)據(jù)是什么,從而提供該行版本信息,讓用戶實現(xiàn)非鎖定一致性讀取。
undo log是采用段(segment)的方式來記錄的,每個undo操作在記錄的時候占用一個undo log segment。
另外, undo log也會產生redo log,因為undo log也要實現(xiàn)持久性保護。

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默認存放在共享表空間中。
    
[root@xuexi 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,即全部寫入一個表空間文件。該變量為靜態(tài)變量,只能在數(shù)據(jù)庫示例停止狀態(tài)下修改,如寫入配置文件或啟動時帶上對應參數(shù)。但是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,因為后續(xù)還可能會用到undo log,如隔離級別為repeatable read時,事務讀取的都是開啟事務時的最新提交行版本,只要該事務不結束,該行版本就不能刪除,即undo log不能刪除。
但是在事務提交的時候,會將該事務對應的undo log放入到刪除列表中,未來通過purge來刪除。并且提交事務時,還會判斷undo log分配的頁是否可以重用,如果可以重用,則會分配給后面來的事務,避免為每個獨立的事務分配獨立的undo log頁而浪費存儲空間和性能。
通過undo log記錄delete和update操作的結果發(fā)現(xiàn):(insert操作無需分析,就是插入行而已)
  • delete操作實際上不會直接刪除,而是將delete對象打上delete flag,標記為刪除,最終的刪除操作是purge線程完成的。
  • update分為兩種情況:update的列是否是主鍵列。
    • 如果不是主鍵列,在undo log中直接反向記錄是如何update的。即update是直接進行的。
    • 如果是主鍵列,update分兩部執(zhí)行:先刪除該行,再插入一行目標行。
?

3.binlog和事務日志的先后順序及group commit

如果事務不是只讀事務,即涉及到了數(shù)據(jù)的修改,默認情況下會在commit的時候調用fsync()將日志刷到磁盤,保證事務的持久性。

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

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

在MySQL5.6以前,當事務提交(即發(fā)出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階段。

精講MySQL事務日志:redo log 和 undo log
  • flush階段:向內存中寫入每個事務的二進制日志。
  • sync階段:將內存中的二進制日志刷盤。若隊列中有多個事務,那么僅一次fsync操作就完成了二進制日志的刷盤操作。這在MySQL5.6中稱為BLGC(binary log group commit)。
  • commit階段:leader根據(jù)順序調用存儲引擎層事務的提交,由于innodb本就支持group commit,所以解決了因為鎖 prepare_commit_mutex 而導致的group commit失效問題。
在flush階段寫入二進制日志到內存中,但是不是寫完就進入sync階段的,而是要等待一定的時間,多積累幾個事務的binlog一起進入sync階段,等待時間由變量 binlog_max_flush_queue_time 決定,默認值為0表示不等待直接進入sync,設置該變量為一個大于0的值的好處是group中的事務多了,性能會好一些,但是這樣會導致事務的響應時間變慢,所以建議不要修改該變量的值,除非事務量非常多并且不斷的在寫入和更新。
進入到sync階段,會將binlog從內存中刷入到磁盤,刷入的數(shù)量和單獨的二進制日志刷盤一樣,由變量 sync_binlog 控制。
當有一組事務在進行commit階段時,其他新事務可以進行flush階段,它們本就不會相互阻塞,所以group commit會不斷生效。當然,group commit的性能和隊列中的事務數(shù)量有關,如果每次隊列中只有1個事務,那么group commit和單獨的commit沒什么區(qū)別,當隊列中事務越來越多時,即提交事務越多越快時,group commit的效果越明顯。

特別推薦一個分享架構+算法的優(yōu)質內容,還沒關注的小伙伴,可以長按關注一下:

精講MySQL事務日志:redo log 和 undo log

精講MySQL事務日志:redo log 和 undo log

精講MySQL事務日志:redo log 和 undo log

長按訂閱更多精彩▼

精講MySQL事務日志:redo log 和 undo log

如有收獲,點個在看,誠摯感謝

免責聲明:本文內容由21ic獲得授權后發(fā)布,版權歸原作者所有,本平臺僅提供信息存儲服務。文章僅代表作者個人觀點,不代表本平臺立場,如有問題,請聯(lián)系我們,謝謝!

本站聲明: 本文章由作者或相關機構授權發(fā)布,目的在于傳遞更多信息,并不代表本站贊同其觀點,本站亦不保證或承諾內容真實性等。需要轉載請聯(lián)系該專欄作者,如若文章內容侵犯您的權益,請及時聯(lián)系本站刪除。
換一批
延伸閱讀

LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: 驅動電源

在工業(yè)自動化蓬勃發(fā)展的當下,工業(yè)電機作為核心動力設備,其驅動電源的性能直接關系到整個系統(tǒng)的穩(wěn)定性和可靠性。其中,反電動勢抑制與過流保護是驅動電源設計中至關重要的兩個環(huán)節(jié),集成化方案的設計成為提升電機驅動性能的關鍵。

關鍵字: 工業(yè)電機 驅動電源

LED 驅動電源作為 LED 照明系統(tǒng)的 “心臟”,其穩(wěn)定性直接決定了整個照明設備的使用壽命。然而,在實際應用中,LED 驅動電源易損壞的問題卻十分常見,不僅增加了維護成本,還影響了用戶體驗。要解決這一問題,需從設計、生...

關鍵字: 驅動電源 照明系統(tǒng) 散熱

根據(jù)LED驅動電源的公式,電感內電流波動大小和電感值成反比,輸出紋波和輸出電容值成反比。所以加大電感值和輸出電容值可以減小紋波。

關鍵字: LED 設計 驅動電源

電動汽車(EV)作為新能源汽車的重要代表,正逐漸成為全球汽車產業(yè)的重要發(fā)展方向。電動汽車的核心技術之一是電機驅動控制系統(tǒng),而絕緣柵雙極型晶體管(IGBT)作為電機驅動系統(tǒng)中的關鍵元件,其性能直接影響到電動汽車的動力性能和...

關鍵字: 電動汽車 新能源 驅動電源

在現(xiàn)代城市建設中,街道及停車場照明作為基礎設施的重要組成部分,其質量和效率直接關系到城市的公共安全、居民生活質量和能源利用效率。隨著科技的進步,高亮度白光發(fā)光二極管(LED)因其獨特的優(yōu)勢逐漸取代傳統(tǒng)光源,成為大功率區(qū)域...

關鍵字: 發(fā)光二極管 驅動電源 LED

LED通用照明設計工程師會遇到許多挑戰(zhàn),如功率密度、功率因數(shù)校正(PFC)、空間受限和可靠性等。

關鍵字: LED 驅動電源 功率因數(shù)校正

在LED照明技術日益普及的今天,LED驅動電源的電磁干擾(EMI)問題成為了一個不可忽視的挑戰(zhàn)。電磁干擾不僅會影響LED燈具的正常工作,還可能對周圍電子設備造成不利影響,甚至引發(fā)系統(tǒng)故障。因此,采取有效的硬件措施來解決L...

關鍵字: LED照明技術 電磁干擾 驅動電源

開關電源具有效率高的特性,而且開關電源的變壓器體積比串聯(lián)穩(wěn)壓型電源的要小得多,電源電路比較整潔,整機重量也有所下降,所以,現(xiàn)在的LED驅動電源

關鍵字: LED 驅動電源 開關電源

LED驅動電源是把電源供應轉換為特定的電壓電流以驅動LED發(fā)光的電壓轉換器,通常情況下:LED驅動電源的輸入包括高壓工頻交流(即市電)、低壓直流、高壓直流、低壓高頻交流(如電子變壓器的輸出)等。

關鍵字: LED 隧道燈 驅動電源
關閉