innodb事務日誌包括redo log和undo log。redo log是重作日誌,提供前滾操做,undo log是回滾日誌,提供回滾操做。html
undo log不是redo log的逆向過程,其實它們都算是用來恢復的日誌:
1.redo log一般是物理日誌,記錄的是數據頁的物理修改,而不是某一行或某幾行修改爲怎樣怎樣,它用來恢復提交後的物理數據頁(恢復數據頁,且只能恢復到最後一次提交的位置)。
2.undo用來回滾行記錄到某個版本。undo log通常是邏輯日誌,根據每行記錄進行記錄。mysql
二進制日誌相關內容,參考:MariaDB/MySQL的二進制日誌。sql
redo log不是二進制日誌。雖然二進制日誌中也記錄了innodb表的不少操做,也能實現重作的功能,可是它們之間有很大區別。數據庫
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中,過程以下: app
在此處須要注意一點,通常所說的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。異步
另外,之因此要通過一層os buffer,是由於open日誌文件的時候,open沒有使用O_DIRECT標誌位,該標誌位意味着繞過操做系統層的os buffer,IO直寫到底層存儲設備。不使用該標誌位意味着將日誌進行緩衝,緩衝到了必定容量,或者顯式fsync()纔會將緩衝中的刷到存儲設備。使用該標誌位意味着每次都要發起系統調用。好比寫abcde,不使用o_direct將只發起一次系統調用,使用o_object將發起5次系統調用。async
MySQL支持用戶自定義在commit時如何將log buffer中的日誌刷log file中。這種控制經過變量 innodb_flush_log_at_trx_commit 的值來決定。該變量有3種值:0、一、2,默認爲1。但注意,這個變量只是控制commit動做是否刷新log buffer到磁盤。
注意,有一個變量 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的時候性能基本都是不變的。關於這個頻率是控制什麼的,在後面的"刷日誌到磁盤的規則"中會說。
在主從複製結構中,要保證事務的持久性和一致性,須要對日誌相關變量設置爲以下:
上述兩項變量的設置保證了:每次提交事務都寫入二進制日誌和事務日誌,並在提交時將它們刷新到磁盤中。
選擇刷日誌的時間會嚴重影響數據修改時的性能,特別是刷到磁盤的過程。下例就測試了 innodb_flush_log_at_trx_commit 分別爲0、一、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)
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塊頭的第三部分 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組成。以下圖:
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 | ./ | +-----------------------------+----------+ [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
能夠看到在默認的數據目錄下,有兩個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。
由於innodb存儲引擎存儲數據的單元是頁(和SQL Server中同樣),因此redo log也是基於頁的格式來記錄的。默認狀況下,innodb的頁大小是16KB(由 innodb_page_size 變量控制),一個頁內能夠存放很是多的log block(每一個512字節),而log block中記錄的又是數據頁的變化。
其中log block中492字節的部分是log body,該log body的格式分爲4部分:
以下圖,分別是insert和delete大體的記錄方式。
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位置。
內存中(buffer pool)未刷到磁盤的數據稱爲髒數據(dirty data)。因爲數據和日誌都以頁的形式存在,因此髒頁表示髒數據和髒日誌。
上一節介紹了日誌是什麼時候刷到磁盤的,不只僅是日誌須要刷盤,髒數據頁也同樣須要刷盤。
在innodb中,數據刷盤的規則只有一個:checkpoint。可是觸發checkpoint的狀況卻有幾種。無論怎樣,checkpoint觸發後,會將buffer中髒數據頁和髒日誌頁都刷到磁盤。
innodb存儲引擎中checkpoint分爲兩種:
因爲刷髒頁須要必定的時間來完成,因此記錄檢查點的位置是在每次刷盤結束以後纔在redo log中標記的。
MySQL中止時是否將髒數據和髒日誌刷入磁盤,由變量innodb_fast_shutdown={ 0|1|2 }控制,默認值爲1,即中止時只作一部分purge,忽略大多數flush操做(但至少會刷日誌),在下次啓動的時候再flush剩餘的內容,實現fast shutdown。
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
其中:
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相等。
在啓動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操做,速度比事務日誌恢復更慢。
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也要實現持久性保護。
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,即所有寫入一個表空間文件。該變量爲靜態變量,只能在數據庫示例中止狀態下修改,如寫入配置文件或啓動時帶上對應參數。可是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
undo相關的變量在MySQL5.6中已經變得不多。以下:它們的意義在上文中已經解釋了。
mysql> show variables like "%undo%"; +-------------------------+-------+ | Variable_name | Value | +-------------------------+-------+ | innodb_undo_directory | . | | innodb_undo_logs | 128 | | innodb_undo_tablespaces | 0 | +-------------------------+-------+
當事務提交的時候,innodb不會當即刪除undo log,由於後續還可能會用到undo log,如隔離級別爲repeatable read時,事務讀取的都是開啓事務時的最新提交行版本,只要該事務不結束,該行版本就不能刪除,即undo log不能刪除。
可是在事務提交的時候,會將該事務對應的undo log放入到刪除列表中,將來經過purge來刪除。而且提交事務時,還會判斷undo log分配的頁是否能夠重用,若是能夠重用,則會分配給後面來的事務,避免爲每一個獨立的事務分配獨立的undo log頁而浪費存儲空間和性能。
經過undo log記錄delete和update操做的結果發現:(insert操做無需分析,就是插入行而已)
若是事務不是隻讀事務,即涉及到了數據的修改,默認狀況下會在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階段的,而是要等待必定的時間,多積累幾個事務的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的效果越明顯。