MYSQL专题-MySQL三大日志binlog、redo log和undo log
日志是mysql數據庫的重要組成部分,記錄著數據庫運行期間各種狀態信息。mysql日志主要包括重做日志(redo log)、回滾日志(undo log)、二進制日志(bin log)、錯誤日志(error log)、慢查詢日志(slow query log)、一般查詢日志(general log),中繼日志(relay log)。作為開發,我們重點需要關注的是二進制日志(binlog)和事務日志(包括redo log和undo log),因為他們都與事務操作息息相關。今天跟大家介紹這三種日志。
二進制日志(bin log)
基本概念
bin log由Mysql的Server層實現,是邏輯日志,記錄的是sql語句的原始邏輯,記錄數據庫執行的寫入性操作(不包括查詢)信息,以二進制的形式保存在磁盤中。什么是邏輯日志,與之對應的還有一個物理日志,介紹一下基本概念:
- 邏輯日志:可以簡單理解為記錄的就是sql語句;
- 物理日志:因為mysql數據最終是保存在數據頁中的,物理日志記錄的就是數據頁變更。
使用任何存儲引擎的mysql數據庫都會記錄binlog日志,通過追加的方式寫入,可以通過max_binlog_size參數設置每個binlog文件的大小,當文件大小達到給定值之后,會生成新的文件來保存日志。同時可設置參數expire_logs_days,在生成時間超過配置的天數之后,會被自動刪除。
使用場景
在實際應用中,binlog的主要使用場景有兩個,分別是主從復制和數據恢復:
- 主從復制:在Master端開啟binlog,然后將binlog發送到各個Slave端,Slave端重放binlog從而達到主從數據一致;
- 數據恢復:通過使用mysqlbinlog工具來恢復數據。
存儲格式
binlog日志有三種格式,分別為STATMENT、ROW和MIXED。在 MySQL 5.7.7之前,默認的格式是STATEMENT,MySQL 5.7.7之后,默認值是ROW。日志格式可以通過binlog-format指定。
(1)STATMENT
基于SQL語句的復制(statement-based replication, SBR),每一條會修改數據的sql語句會記錄到binlog中。
- 優點:不需要記錄每一行的變化,減少了binlog日志量,節約了IO, 從而提高了性能;
- 缺點:在某些情況下會導致主從數據不一致甚至出錯,比如執行sysdate()、slepp()等。
(2)ROW
基于行的復制(row-based replication, RBR),不記錄每條sql語句的上下文信息,僅需記錄哪條數據被修改了。
- 優點:不會出現某些特定情況下的存儲過程、或function、或trigger的調用和觸發無法被正確復制的問題;
- 缺點:磁盤占用會比其他兩種模式大很多,在一些大表中清除大量數據時在 binlog 中會生成很多條語句,可能導致從庫延遲變大,會產生大量的日志。
(3)MIXED
基于STATMENT和ROW兩種模式的混合復制(mixed-based replication, MBR),根據語句來選用是 statement 還是 row 模式。一般的復制使用STATEMENT模式保存binlog,如表結構變更,如果 SQL 語句是 update 或者 delete 語句,或者對于STATEMENT模式無法復制的操作使用ROW模式保存binlog。
刷盤時機
對于InnoDB存儲引擎而言,只有在事務提交時才會記錄biglog,在那之前記錄還在內存中。那么biglog是什么時候刷到磁盤中的呢?mysql通過sync_binlog參數控制biglog的刷盤時機,取值范圍是0-N:
- 0:不去強制要求,由系統自行判斷何時寫入磁盤;
- 1:每次commit的時候都要將binlog寫入磁盤;
- N:每N個事務,才會將binlog寫入磁盤。
MySQL 5.7.7之后版本的默認值為1,但是設置一個大一些的值可以提升數據庫性能,因此實際情況下也可以將值適當調大,犧牲一定的一致性來獲取更好的性能。
重做日志(redo log)
基本概念
redo log由引擎層的InnoDB引擎實現,是物理日志,記錄的是物理數據頁修改的信息,包括兩部分:
- 內存中的日志緩沖(redo log buffer),
- 磁盤上的日志文件(redo log file)。
mysql每執行一條DML語句,先將記錄寫入redo log buffer,后續某個時間點再一次性將多個操作記錄寫到redo log file。這種先寫日志,再寫磁盤的技術就是MySQL里經常說到的WAL(Write-Ahead Logging) 技術。redo log是順序寫入指定大小的物理文件中的,是循環寫入。當文件快寫滿時,會邊擦除邊刷磁盤,即擦除日志記錄(redo log file)并將數據刷到磁盤中。
使用場景
事務的四大特性里面有一個是持久性,具體來說就是只要事務提交成功,那么對數據庫做的修改就被永久保存下來了,不可能因為任何原因再回到原來的狀態。那么mysql是如何保證一致性的呢?最簡單的做法是在每次事務提交的時候,將該事務涉及修改的數據頁全部刷新到磁盤中。但是這么做會有嚴重的性能問題,主要體現在兩個方面:
- Innodb是以頁為單位進行磁盤交互的,而一個事務很可能只修改一個數據頁里面的幾個字節,這個時候將完整的數據頁刷到磁盤的話,太浪費資源了!
- 一個事務可能涉及修改多個數據頁,并且這些數據頁在物理上并不連續,使用隨機IO寫入性能太差
因此mysql設計了redo log,具體來說就是只記錄事務對數據頁做了哪些修改,這樣就能完美地解決性能問題了(相對而言文件更小并且是順序IO)。總結起來redo log的作用如下:
- 提供crash-safe 能力(崩潰恢復),確保事務的持久性;
- 利用WAL技術推遲物理數據頁的刷新,從而提升數據庫吞吐,有效降低了訪問時延。
存儲格式
(1)日志塊
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來記錄該數據頁的變化。關于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組成。如下圖:
(2) 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 | ./ | +-----------------------------+----------+[root@kmli 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繼續寫入。如下我們定義了四個文件:
在innodb中,既有redo log需要刷盤,還有數據頁也需要刷盤,redo log存在的意義主要就是降低對數據頁刷盤的要求。在上圖中,write pos表示redo log當前記錄的LSN(邏輯序列號)位置,check point表示數據頁更改記錄刷盤后對應redo log所處的LSN(邏輯序列號)位置。write pos到check point之間的部分是redo log空著的部分,用于記錄新的記錄;check point到write pos之間是redo log待落盤的數據頁更改記錄。當write pos追上check point時,會先推動check point向前移動,空出位置再記錄新的日志。
在每個組的第一個redo log file中,前2KB記錄4個特定的部分,從2KB之后才開始記錄log block。除了第一個redo log file中會記錄,log group中的其他log file不會記錄這2KB,但是卻會騰出這2KB的空間。如下:
(3)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大致的記錄方式;
啟動innodb的時候,不管上次是正常關閉還是異常關閉,總是會進行恢復操作。因為redo log記錄的是數據頁的物理變化,因此恢復的時候速度比邏輯日志(如binlog)要快很多。重啟innodb時,首先會檢查磁盤中數據頁的LSN,如果數據頁的LSN小于日志中的LSN,則會從checkpoint開始恢復。還有一種情況,在宕機前正處于checkpoint的刷盤過程,且數據頁的刷盤進度超過了日志頁的刷盤進度,此時會出現數據頁中記錄的LSN大于日志中的LSN,這時超出日志進度的部分將不會重做,因為這本身就表示已經做過的事情,無需再重做。另外,事務日志具有冪等性,所以多次操作得到同一結果的行為在日志中只記錄一次。而二進制日志不具有冪等性,多次操作會全部記錄下來,在恢復的時候會多次執行二進制日志中的記錄,速度就慢得多。例如,某記錄中id初始值為2,通過update將值設置為了3,后來又設置成了2,在事務日志中記錄的將是無變化的頁,根本無需恢復;而二進制會記錄下兩次update操作,恢復時也將執行這兩次update操作,速度比事務日志恢復更慢。
對于redo log的存儲的內容其實遠不止這些,里面還涉及到很多的地方,這里只是作為大家對這些概念的理解以及入門,后面還有作進一步的講解。
刷盤時機
在計算機操作系統中,用戶空間(user space)下的緩沖區數據一般情況下是無法直接寫入磁盤的,中間必須經過操作系統內核空間(kernel space)緩沖區(OS Buffer)。因此,redo log buffer寫入redo log file實際上是先寫入OS Buffer,然后再通過系統調用fsync()將其刷到redo log file中,過程如下:
mysql支持三種將redo log buffer寫入redo log file的時機,可以通過innodb_flush_log_at_trx_commit參數配置,各參數值含義如下:
| 0 | 延遲寫。事務提交時不會將redo log buffer中的日志寫入os buffer,而是每秒寫入os buffer并調用fsync()寫入到redo log file中。即大約每秒刷新數據到磁盤中,當系統崩潰時,會丟失一秒的數據 |
| 1 | 實時寫,實時刷。事務每次提交都會將redo log buffer中的日志寫入os bufferr并調用fsync()寫入到redo log file中。這種方式即使系統崩潰也不會丟失任何數據,但因為每次提交都要寫入磁盤,IO性能較差 |
| 2 | 實時寫,延遲刷。事務每次提交都只會將日志寫入os bufferr,然后是每秒調用fsync()將os buffer中的日志寫入到redo log file |
每種方式的存儲過程如下圖所示:
在主從復制結構中,要保證事務的持久性和一致性,需要對日志相關變量設置為如下:
- 如果啟用了二進制日志,則設置sync_binlog=1,即每提交一次事務同步寫到磁盤中。
- 總是設置innodb_flush_log_at_trx_commit=1,即每提交一次事務都寫到磁盤中。
上述兩項變量的設置保證了:每次提交事務都寫入二進制日志和事務日志,并在提交時將它們刷新到磁盤中。
總結完bin log和redo log,我們在這里做一下兩者的對比:
| 文件大小 | 可通過配置參數max_binlog_size設置每個binlog文件的大小 | 大小固定 |
| 實現方式 | Server層實現,所有引擎都可以使用 | InnoDB引擎實現,不是所有引擎都有 |
| 記錄方式 | 追加方式,文件大小大于給定值時,記錄到新文件上 | 循壞記錄,寫到結尾時,會回到開頭寫 |
| 適用場景 | 主從復制和數據恢復 | 崩潰恢復 |
回滾日志(undo log)
基本概念
undo log由引擎層的InnoDB引擎實現,是邏輯日志,記錄數據修改被修改前的值。當一條數據需要更新前,會先把修改前的記錄存儲在undolog中,如果這個修改出現異常,則會使用undo日志來實現回滾操作,保證事務的一致性。當事務提交之后,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分兩部執行:先刪除該行,再插入一行目標行。
使用場景
數據庫事務四大特性中有一個是原子性,原子性底層就是通過undo log實現的。undo log主要記錄了數據的邏輯變化,比如一條INSERT語句,對應一條DELETE的undo log,對于每個UPDATE語句,對應一條相反的UPDATE的undo log,這樣在發生錯誤時,就能回滾到事務之前的數據狀態:
- 保存事務發生之前的數據的一個版本,用于回滾;
- 可以提供多版本并發控制下的讀(MVCC),也即非鎖定讀。
存儲格式
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@kmli 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的值:
2010-12-08 13:16:00 7f665bfab720 InnoDB: Expected to open 3 undo tablespaces but was able 2010-12-08 13:16:00 7f665bfab720 InnoDB: to find only 0 undo tablespaces. 2010-12-08 13:16:00 7f665bfab720 InnoDB: Set the innodb_undo_tablespaces parameter to the 2010-12-08 13:16:00 7f665bfab720 InnoDB: correct value and retry. Suggested value is 0猜你感興趣:
MYSQL專題-絕對實用的MYSQL優化總結
MYSQL專題-MySQL事務實現原理
MYSQL專題-MVCC多版本并發控制
MYSQL專題-使用Binlog日志恢復MySQL數據
更多文章請點擊:更多…
總結
以上是生活随笔為你收集整理的MYSQL专题-MySQL三大日志binlog、redo log和undo log的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: MYSQL专题-绝对实用的MYSQL优化
- 下一篇: MYSQL专题-MySQL事务实现原理