來源: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ū)別
-
二進制日志是在 存儲引擎的上層產生的,不管是什么存儲引擎,對數(shù)據(jù)庫進行了修改都會產生二進制日志。而redo log是innodb層產生的,只記錄該存儲引擎中表的修改。 并且二進制日志先于redo log被記錄。具體的見后文group commit小結。 -
二進制日志記錄操作的方法是邏輯性的語句。即便它是基于行格式的記錄方式,其本質也還是邏輯的SQL設置,如該行記錄的每列的值是多少。而redo log是在物理格式上的日志,它記錄的是數(shù)據(jù)庫中每個頁的修改。 -
二進制日志只在每次事務提交的時候一次性寫入緩存中的日志"文件"(對于非事務表的操作,則是每次執(zhí)行語句成功后就直接寫入)。而redo log在數(shù)據(jù)準備修改前寫入緩存中的redo log中,然后才對緩存中的數(shù)據(jù)執(zhí)行修改操作;而且保證在發(fā)出事務提交指令時,先向緩存中的redo log寫入日志,寫入完成后才執(zhí)行提交動作。 -
因為二進制日志只在提交的時候一次性寫入,所以二進制日志中的記錄方式和提交順序有關,且一次提交對應一次記錄。而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*?。 -
事務日志記錄的是物理頁的情況,它具有冪等性,因此記錄日志的方式極其簡練。冪等性的意思是多次操作前后狀態(tài)是一樣的,例如新插入一行后又刪除該行,前后狀態(tài)沒有變化。而二進制日志記錄的是所有影響數(shù)據(jù)的操作,記錄的內容較多。例如插入一行記錄一次,刪除該行又記錄一次。
1.2 redo log的基本概念
-
當設置為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。
-
如果啟用了二進制日志,則設置sync_binlog=1,即每提交一次事務同步寫到磁盤中。 -
總是設置innodb_flush_log_at_trx_commit=1,即每提交一次事務都寫到磁盤中。
#創(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條記錄的時間。
call proc(100000);Query OK, 0 rows affected (15.48 sec)
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)
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)
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 ;
mysql> set @@global.innodb_flush_log_at_trx_commit=1;
mysql>?truncate?test_flush_log;
call proc(1000000);
Query OK, 0 rows affected (11.26 sec)
1.3 日志塊(log block)
-
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é))寫入檢查點信息的位置。
1.4 log group和redo log file
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
1.5 redo log的格式
-
redo_log_type:占用1個字節(jié),表示redo log的日志類型。 -
space:表示表空間的ID,采用壓縮的方式后,占用的空間可能小于4字節(jié)。 -
page_no:表示頁的偏移量,同樣是壓縮過的。 -
redo_log_body表示每個重做日志的數(shù)據(jù)部分,恢復時會調用相應的函數(shù)進行解析。例如insert語句和delete語句寫入redo log的內容是不一樣的。
1.6 日志刷盤的規(guī)則
1.7 數(shù)據(jù)頁刷盤的規(guī)則及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后,就強制刷一部分臟頁到磁盤。
1.8 LSN超詳細分析
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。
log sequence number(110) > log flushed up to(100) = pages flushed up to = last checkpoint at
log sequence number(150) = log flushed up to > pages flushed up to(100) = last checkpoint at
log sequence number > log flushed up to 和 pages flushed up to > last checkpoint at
log sequence number = log flushed up to > pages flushed up to = last checkpoint at
1.9 innodb的恢復行為
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 基本概念
2.2 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
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相關的變量
mysql> show variables like "%undo%";
+-------------------------+-------+
| Variable_name | Value |
+-------------------------+-------+
| innodb_undo_directory | . |
| innodb_undo_logs | 128 |
| innodb_undo_tablespaces | 0 |
+-------------------------+-------+
2.4 delete/update操作的內部機制
-
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階段。
-
flush階段:向內存中寫入每個事務的二進制日志。 -
sync階段:將內存中的二進制日志刷盤。若隊列中有多個事務,那么僅一次fsync操作就完成了二進制日志的刷盤操作。這在MySQL5.6中稱為BLGC(binary log group commit)。 -
commit階段:leader根據(jù)順序調用存儲引擎層事務的提交,由于innodb本就支持group commit,所以解決了因為鎖 prepare_commit_mutex 而導致的group commit失效問題。
特別推薦一個分享架構+算法的優(yōu)質內容,還沒關注的小伙伴,可以長按關注一下:

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





