天天看點

mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

文章目錄

  • 1. [重做日志 `redo log` ](https://www.bilibili.com/video/BV1hB4y1F7Bx?p=2)
    • 1.1. 介紹
    • 1.2. 工作原理
    • 1.3. redolog寫入機制
    • 1.4. 為何資料更新不直接寫到磁盤上去? 還搞redo buffer等?
    • 1.5. 持久化到redolog的時間不耗時麼?
    • 1.6. redolog 相關配置參數
  • 2. [歸檔日志 `binlog`](https://www.bilibili.com/video/BV1tN411Z7iJ)
    • 2.1. 介紹
    • 2.2. 開啟binlog的場景
    • 2.3. 檔案記錄模式有三種
    • 2.4. binlog檔案結構
    • 2.5. binlog寫入機制
    • 2.6. binlog的實際操作
  • 3. redolog和binlog的差別
    • 3.1. 為何會有兩份日志 redolog和binlog呢?
    • 3.2. 為什麼必須有“兩階段送出”呢?
    • 3.3. 建議:
    • 3.4. 學習收獲
  • 4. 事務復原日志 undolog
    • 4.1. undolog 介紹
    • 4.2. undolog 作用
  • 5. 總結
mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

一條更新語句的執行流程

學習視訊 redolog undolog binlog

讀完下文, 你應該明白 MySQL可以恢複到半個月内任意一秒的狀态是怎麼操作的

mysql> create table T(ID int primary key, c int); # 建立表
mysql> update T set c=c+1 where ID=2; # 更新表
           

前面我們說過,在一個表上有更新的時候,跟這個表有關的查詢緩存會失效,是以這條語句就會把表T上所有緩存結果都清空。這也就是我們一般不建議使用查詢緩存的原因。

  1. 連接配接器 連結資料庫
  2. 分析器 詞法确定更新語句, 文法解析判斷是否能有表T和字段ID和c
  3. 優化器 發現有索引ID可用
  4. 執行器 找到這一行, 更新

與查詢流程不一樣的是,更新流程還涉及兩個重要的日志子產品,它們正是我們今天要讨論的主角:

redo log

(重做日志)和

bin log

(歸檔日志)。如果接觸MySQL,那這兩個詞肯定是繞不過的,我後面的内容裡也會不斷地和你強調。不過話說回來,redo log和binlog在設計上有很多有意思的地方,這些設計思路也可以用到你自己的程式裡。

1. 重做日志

redo log

不知道你還記不記得《孔乙己》這篇文章,酒店掌櫃有一個粉闆,專門用來記錄客人的賒賬記錄。如果賒賬的人不多,那麼他可以把顧客名和賬目寫在闆上。但如果賒賬的人多了,粉闆總會有記不下的時候,這個時候掌櫃一定還有一個專門記錄賒賬的賬本。

如果有人要賒賬或者還賬的話,掌櫃一般有兩種做法:

一種做法是直接把賬本翻出來,把這次賒的賬加上去或者扣除掉;

另一種做法是先在粉闆上記下這次的賬,等打烊以後再把賬本翻出來核算。

在生意紅火櫃台很忙時,掌櫃一定會選擇後者,因為前者操作實在是太麻煩了。首先,你得找到這個人的賒賬總額那條記錄。你想想,密密麻麻幾十頁,掌櫃要找到那個名字,可能還得帶上老花鏡慢慢找,找到之後再拿出算盤計算,最後再将結果寫回到賬本上。

這整個過程想想都麻煩。相比之下,還是先在粉闆上記一下友善。你想想,如果掌櫃沒有粉闆的幫助,每次記賬都得翻賬本,效率是不是低得讓人難以忍受?

同樣,在MySQL裡也有這個問題,如果每一次的更新操作都需要寫進磁盤,然後磁盤也要找到對應的那條記錄,然後再更新,整個過程IO成本、查找成本都很高。為了解決這個問題,MySQL的設計者就用了類似酒店掌櫃粉闆的思路來提升更新效率。

而粉闆和賬本配合的整個過程,其實就是MySQL裡經常說到的WAL技術,WAL的全稱是Write-Ahead Logging,它的關鍵點就是先寫日志,再寫磁盤,也就是先寫粉闆,等不忙的時候再寫賬本。

具體來說,當有一條記錄需要更新的時候,InnoDB引擎就會先把記錄寫到redo log(粉闆)裡面,并更新記憶體,這個時候更新就算完成了。同時,InnoDB引擎會在适當的時候,将這個操作記錄更新到磁盤裡面,而這個更新往往是在系統比較空閑的時候做,這就像打烊以後掌櫃做的事。

如果今天賒賬的不多,掌櫃可以等打烊後再整理。但如果某天賒賬的特别多,粉闆寫滿了,又怎麼辦呢?這個時候掌櫃隻好放下手中的活兒,把粉闆中的一部分賒賬記錄更新到賬本中,然後把這些記錄從粉闆上擦掉,為記新賬騰出空間。

與此類似,InnoDB的redo log是固定大小的,比如可以配置為一組4個檔案,每個檔案的大小是1GB,那麼這塊“粉闆”總共就可以記錄4GB的操作。從頭開始寫,寫到末尾就又回到開頭循環寫,這就是redolog的寫入機制.

1.1. 介紹

  1. redolog是InnoDB資料庫存儲引擎的特有日志, 也就是收redolog屬于引擎層
  2. redo的意思是重做, 以恢複操作為目的, 在資料庫發生意外時重制操作
  3. redolog指的是事務中修改任何資料, 将最新的資料備份存儲的位置(redolog), 被稱為重做日志
  4. 生成/銷毀: 随着事務操作的執行, 會生成redolog , 在事務送出時, 産生redolog寫入log Buffer, 并不是随着事物的送出就立即寫入磁盤; 等待事務操作的髒頁寫入磁盤後, redolog使命完成, redolog占用空間就可以重用了(覆寫寫入)
  5. 髒頁指的是記憶體和磁盤資料不一緻, 比如記憶體裡面更改了, 但是還沒應用到磁盤中

1.2. 工作原理

redolog是為了實作事務的持久化而出現的産物, 防止在發生故障時間點, 尚有髒頁未寫入表IBD檔案中, 在重新開機MySQL服務時, 根據redolog進行重做, 進而達到事務的未入磁盤資料進行持久化這一特性;

mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

1.3. redolog寫入機制

mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

redolog檔案内容是順序循環寫入檔案, 寫滿時回溯到第一個檔案進行覆寫寫, 跟隊列一樣;

write pos

是目前記錄的位置,一邊寫一邊後移,寫到第3号檔案末尾後就回到0号檔案開頭。

checkpoint

是目前要擦除的位置,也是往後推移并且循環的,擦除記錄前要把記錄更新到資料檔案。

write pos

checkpoint

之間的是“粉闆”上還空着的部分,可以用來記錄新的操作。如果write pos追上checkpoint,表示“粉闆”滿了,這時候不能再執行新的更新,得停下來先擦掉一些記錄,把checkpoint推進一下。

有了

redo log

,InnoDB就可以保證即使資料庫發生異常重新開機,之前送出的記錄都不會丢失,這個能力稱為crash-safe。

要了解crash-safe這個概念,可以想想我們前面賒賬記錄的例子。隻要賒賬記錄記在了粉闆上或寫在了賬本上,之後即使掌櫃忘記了,比如突然停業幾天,恢複生意後依然可以通過賬本和粉闆上的資料明确賒賬賬目。

1.4. 為何資料更新不直接寫到磁盤上去? 還搞redo buffer等?

如果直接将User更新直接放到磁盤上, 那麼作業系統跟磁盤的互動數量更多, 而作業系統的時間占用尤其磁盤讀寫時間長, 是以為了盡可能的減少磁盤的互動次數, 使用redobuffer等, 先記憶體中攢着, 在持久化, 這樣就可以降低磁盤互動次數;

1.5. 持久化到redolog的時間不耗時麼?

比如更新a=2, 然後更新a=5, 在更新a=8, 這樣持久化到redolog的時間是一個IO, 但是更新到User.IBD, 需要先定位a, 在更新, 是以會占用很多IO時間, 是以利用redolog暫時記錄, 效率會高;

1.6. redolog 相關配置參數

每個InnoDB存儲引擎至少一個重做日志組

group

, 每個檔案組至少兩個重做日志檔案, 預設為

ib_logfile0

ib_logfile1

mysql> show variables like '%innodb_log%';
+-----------------------------+----------+
| Variable_name               | Value    |
+-----------------------------+----------+
| innodb_log_buffer_size      | 16777216 |
| innodb_log_checksums        | ON       |
| innodb_log_compressed_pages | ON       |
| innodb_log_file_size        | 50331648 |
| innodb_log_files_in_group   | 2        |
| innodb_log_group_home_dir   | ./       |
| innodb_log_write_ahead_size | 8192     |
+-----------------------------+----------+
7 rows in set (0.01 sec)

mysql>
           

redo buffer

持久化到

redolog

政策可以通過

innodb_flush_log_at_trx_commit

設定;

  • 0:

    buffer pool

    随着事務送出commit, 寫入到

    logBuffer

    不直接寫入到log檔案中, 背景有個線程将

    logBuffer

    1s寫到硬碟一次(

    OS cache

    負責); 這樣可能會造成1s的資料丢失
  • 1: commit送出時會直接寫

    OS cache

    , 作業系統就會直接寫到磁盤中; 最安全, 但是性能最差
  • 2: commit送出時會寫入

    log BUffer

    在寫入

    OS cache

    , 但是作業系統不會直接寫入到磁盤, 而是1s執行

    flush cache to disk

    操作;

總之:

  • 事務

    commit write

    --> 記憶體

    logBuffer

    --> 作業系統控制記憶體寫入磁盤線程

    OS cache

    --> 日志log(磁盤)
  • 建議設定政策2, 因為

    OS cache

    操作相對安全(隻要作業系統沒死), 間接相當于記憶體成功寫入磁盤中了

2. 歸檔日志

binlog

2.1. 介紹

  1. Server層的日志用于歸檔日志
  2. 記錄事務原子性,
  3. 記錄所有資料庫表結構變更以及資料修改的二進制日志(不會記錄查詢和show這類操作)
  4. binlog日志以事件形式記錄, 包含語句所執行的消耗時間

2.2. 開啟binlog的場景

因為binlog一般是關閉狀态, 占用系統資源較大 1%, 一般隻有下面兩種場景會打開

  1. 主從複制: 主庫開啟, 這樣就可以把binlog傳遞給從庫, 從庫根據binlog恢複表資料, 一緻性
  2. 資料恢複: 萬一有人删除表的情況, 通過

    mysqlbinlog

    工具恢複

2.3. 檔案記錄模式有三種

  1. ROW(row_based_replication, RBP): 日志中會記錄每一行資料被修改情況, 在slave端進行相同資料修改;

    優勢: 實作資料的完全恢複,

    缺點: 更改100行, 會産生100行記錄, 消耗大量資源;

  2. STATMENT(statement-based replication SBR): 每條修改資料的SQL記錄到master的binlog中, slave在複制的時候, SQL程序會解析成和原來master端執行過的相同的SQL在執行, 簡稱SQL複制;

    優勢: 日志量少, 減少磁盤IO, 提升存儲和恢複速度

    缺點: 但是記錄的一些參數會有差異, 比如

    now()

    時間,

    last_insert_id()

    等函數, 這樣就不能恢複這條資料了
  3. MIXED(mixed-based replication MBR): 上面兩種模式混合使用, 一般使用statement模式儲存binlog, 對于statement模式無法複制的操作使用row儲存, mysql會根據執行的sql語句選擇寫入模式

是以主從複制使用記錄模式1

ROW模式

2.4. binlog檔案結構

mysql的binlog檔案中記錄的是對資料庫的各種修改操作, 用來表示修改操作的資料結構是

log event

, 不同的修改操作對應的不同的

log event

比較常用的

log event

有:

Query event

,

Row event

,

XID Event

binlog檔案的内容是各種log event集合

  • log event

    結構
mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

2.5. binlog寫入機制

  • 根據記錄模式和操作觸發event時間生成log event(事件觸發執行機制)
  • 事務執行過程中産生的log event寫入緩沖區, 每個事務線程都有一個緩沖區, 儲存成

    binlog_cache_mngr資料結構

    , 這個結構中有兩個緩沖區(1, stmt_cache 存放不支援事務資訊, 2,trx_cache, 存放支援事務的資訊)
  • 事務送出, 會将log event寫入外部binlog檔案中;

注意: 不同僚務以串行方式寫入binlog檔案中, 是以一個事務包含一個log event資訊

2.6. binlog的實際操作

下文指令流程實作的是從建立資料庫, 建立資料庫表, 插入和更新資料到删表删庫跑路, 在利用binlog恢複資料庫的操作流程;

  • 啟動binlog
mysql> show variables like 'log_bin'; # 檢視binlog是否開啟
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin       | OFF   | # 未開啟
+---------------+-------+
1 row in set (0.16 sec)

# 通過設定mysql底層檔案, 開啟binlog功能
sudo vi /etc/mysql/mysql.conf.d/mysqld.cnf 
[mysqld]
# add by zjq
log-bin=mysql-bin
server-id=1
binlog-format=ROW # 指定ROW值得改變進行儲存, 更費空間, 完整儲存

# 重新開機mysql
sudo service mysql restart

mysql> show variables like '%log_bin%';
+---------------------------------+--------------------------------+
| Variable_name                   | Value                          |
+---------------------------------+--------------------------------+
| log_bin                         | ON                             |
| log_bin_basename                | /var/lib/mysql/mysql-bin       |
| log_bin_index                   | /var/lib/mysql/mysql-bin.index |
| log_bin_trust_function_creators | OFF                            |
| log_bin_use_v1_row_events       | OFF                            |
| sql_log_bin                     | ON                             |
+---------------------------------+--------------------------------+
6 rows in set (0.01 sec)
           
  • 檢視日志
mysql> show binary logs;
mysql> show master logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       177 |
| mysql-bin.000002 |       154 |
+------------------+-----------+
mysql> show master status;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000002 |      154 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+
1 row in set (0.00 sec)

mysql> show binlog events;
mysql> show binlog events in 'mysql-bin.000002';
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| Log_name         | Pos | Event_type     | Server_id | End_log_pos | Info                                                   |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
| mysql-bin.000002 |   4 | Format_desc    |         1 |         123 | Server ver: 5.7.33-0ubuntu0.18.04.1-log, Binlog ver: 4 |
| mysql-bin.000002 | 123 | Previous_gtids |         1 |         154 |                                                        |
+------------------+-----+----------------+-----------+-------------+--------------------------------------------------------+
2 rows in set (0.00 sec)

# 這些是在shell下檢視的
mysqlbinlog "檔案名"
mysqlbinlog "檔案名" > "test.sql" # 可以通過檔案檢視了
           
  • 利用binlog恢複資料
# 按時間恢複
mysqlbinlog --start-datetime="2020-04-25 18:00:00" --stop-datetime="2020-04-26 18:00:00" mysqlbinlog.000002 | mysql -uroot -p123456

# 按時間位置号恢複
mysqlbinlog --start-position=154 --stop-position=356 mysqlbinlog.000002 | mysql -uroot -p123456
           
  • 實際操作
mysql> show databases; # 顯示資料庫所有
mysql> create database mytest default character set utf8; # 建立資料庫mytest
Query OK, 1 row affected (0.07 sec)

mysql> show databases; # 顯示資料庫所有
+--------------------+
| Database           |
+--------------------+
| mytest             |
+--------------------+

mysql> use mytest; # 選擇資料庫mytest
Database changed
mysql> create table user( # 建立表
    -> id int primary key,
    -> name varchar(20)
    -> ) engine=innodb
    -> charset=utf8;
Query OK, 0 rows affected (0.47 sec)

mysql> insert into user values (1, "tom"); # 插入資料
mysql> insert into user values (2, "scott");
mysql> select * from user; # 查詢表
+----+-------+
| id | name  |
+----+-------+
|  1 | tom   |
|  2 | scott |
+----+-------+
mysql> update user set name="TOM" where id = 1; # 更新資料
mysql> drop database mytest; # 删表
mysql> select * from user;
ERROR 1046 (3D000): No database selected
mysql> show master logs; # 顯示目前有多少個binlog檔案
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000001 |       177 |
| mysql-bin.000002 |      1546 |
+------------------+-----------+

mysql> show binlog events in "mysql-bin.000002" ;  # 檢視目前所有的binlog記錄
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------+
| Log_name         | Pos  | Event_type     | Server_id | End_log_pos | Info                                                                                               |
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------+
| mysql-bin.000002 |    4 | Format_desc    |         1 |         123 | Server ver: 5.7.33-0ubuntu0.18.04.1-log, Binlog ver: 4                                             |
| mysql-bin.000002 |  123 | Previous_gtids |         1 |         154 |                                                                                                    |
| mysql-bin.000002 |  154 | Anonymous_Gtid |         1 |         219 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 |  219 | Query          |         1 |         346 | create database mytest default character set utf8                                                  |
| mysql-bin.000002 |  346 | Anonymous_Gtid |         1 |         411 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 |  411 | Query          |         1 |         573 | use `mytest`; create table user(
id int primary key,
name varchar(20)
) engine=innodb
charset=utf8 |
| mysql-bin.000002 |  573 | Anonymous_Gtid |         1 |         638 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 |  638 | Query          |         1 |         712 | BEGIN                                                                                              |
| mysql-bin.000002 |  712 | Table_map      |         1 |         764 | table_id: 108 (mytest.user)                                                                        |
| mysql-bin.000002 |  764 | Write_rows     |         1 |         808 | table_id: 108 flags: STMT_END_F                                                                    |
| mysql-bin.000002 |  808 | Xid            |         1 |         839 | COMMIT /* xid=22 */                                                                                |
| mysql-bin.000002 |  839 | Anonymous_Gtid |         1 |         904 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 |  904 | Query          |         1 |         978 | BEGIN                                                                                              |
| mysql-bin.000002 |  978 | Table_map      |         1 |        1030 | table_id: 108 (mytest.user)                                                                        |
| mysql-bin.000002 | 1030 | Write_rows     |         1 |        1076 | table_id: 108 flags: STMT_END_F                                                                    |
| mysql-bin.000002 | 1076 | Xid            |         1 |        1107 | COMMIT /* xid=23 */                                                                                |
| mysql-bin.000002 | 1107 | Anonymous_Gtid |         1 |        1172 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 | 1172 | Query          |         1 |        1246 | BEGIN                                                                                              |
| mysql-bin.000002 | 1246 | Table_map      |         1 |        1298 | table_id: 108 (mytest.user)                                                                        |
| mysql-bin.000002 | 1298 | Update_rows    |         1 |        1352 | table_id: 108 flags: STMT_END_F                                                                    |
| mysql-bin.000002 | 1352 | Xid            |         1 |        1383 | COMMIT /* xid=25 */                                                                                |
| mysql-bin.000002 | 1383 | Anonymous_Gtid |         1 |        1448 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                               |
| mysql-bin.000002 | 1448 | Query          |         1 |        1546 | drop database mytest                                                                               |
+------------------+------+----------------+-----------+-------------+----------------------------------------------------------------------------------------------------+
23 rows in set (0.00 sec)

# 從 219 建庫到删除前1383恢複, 這個從shell中執行
mysql> mysqlbinlog --start-position=219 --stop-position=1383 mysql-bin.000002 | mysql -uro
ot -p


mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| mytest             |
| performance_schema |
| sys                |
+--------------------+
5 rows in set (0.00 sec)

mysql> use mytest;
mysql> select * from user;
+----+-------+
| id | name  |
+----+-------+
|  1 | TOM   |
|  2 | scott |
+----+-------+
           
  • binlog删除和清除
purge binary logs to "mysqlbinlog.000001"; # 删除指定檔案
purge binary logs before "2020-04-28 00:00:00"; # 删除時間
reset master; # 清除所有
           
  • 日志設定有效期
mysql> set global expire_logs_days=7; # 設定超過7天後删除對應的binlog
mysql> show variables like '%expire_logs_days%'; # 顯示值
+------------------+-------+
| Variable_name    | Value |
+------------------+-------+
| expire_logs_days | 7     |
+------------------+-------+
           

3. redolog和binlog的差別

3.1. 為何會有兩份日志 redolog和binlog呢?

因為最開始MySQL裡并沒有InnoDB引擎。MySQL自帶的引擎是MyISAM,但是MyISAM沒有

crash-safe

的能力,

binlog

日志隻能用于歸檔。而InnoDB是另一個公司以插件形式引入MySQL的,既然隻依靠binlog是沒有

crash-safe

能力的,是以InnoDB使用另外一套日志系統——也就是

redo log

來實作

crash-safe

能力。

這兩種日志有以下三點不同。

  1. redo log是InnoDB引擎特有的;binlog是MySQL的Server層實作的,所有引擎都可以使用, 二進制檔案存儲。
  2. redo log是實體日志,記錄的是資料頁更新狀态内容;binlog是邏輯日志,記錄的是這個語句的原始邏輯, 記錄的更新過程,比如“給ID=2這一行的c字段加1 ”。
  3. redo log是循環寫的,空間固定會用完;binlog是可以追加寫入的。“追加寫”是指binlog檔案寫到一定大小後會切換到下一個,并不會覆寫以前的日志。
  4. redolog解決伺服器異常, 當機後資料恢複, 屬于内部自動機制; binlog作為主從複制和資料恢複使用, 沒有自動恢複能力, 需要手動恢複;

有了對這兩個日志的概念性了解,我們再來看執行器和InnoDB引擎在執行這個簡單的update語句時的内部流程。

執行器先找引擎取ID=2這一行。ID是主鍵,引擎直接用樹搜尋找到這一行。如果ID=2這一行所在的資料頁本來就在記憶體中,就直接傳回給執行器;否則,需要先從磁盤讀入記憶體,然後再傳回。

執行器拿到引擎給的行資料,把這個值加上1,比如原來是N,現在就是N+1,得到新的一行資料,再調用引擎接口寫入這行新資料。

引擎将這行新資料更新到記憶體中,同時将這個更新操作記錄到redo log裡面,此時redo log處于prepare狀态。然後告知執行器執行完成了,随時可以送出事務。

執行器生成這個操作的binlog,并把binlog寫入磁盤。

執行器調用引擎的送出事務接口,引擎把剛剛寫入的redo log改成送出(commit)狀态,更新完成。

這裡我給出這個update語句的執行流程圖,圖中淺色框表示是在InnoDB内部執行的,深色框表示是在執行器中執行的。

mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

你可能注意到了,最後三步看上去有點“繞”,将redo log的寫入拆成了兩個步驟:prepare和commit,這就是"兩階段送出"。

3.2. 為什麼必須有“兩階段送出”呢?

這是為了讓兩份日志之間的邏輯一緻

前面我們說過了,binlog會記錄所有的邏輯操作,并且是采用“追加寫”的形式。如果你的DBA承諾說半個月内可以恢複,那麼備份系統中一定會儲存最近半個月的所有binlog,同時系統會定期做整庫備份。這裡的“定期”取決于系統的重要性,可以是一天一備,也可以是一周一備。

當需要恢複到指定的某一秒時,比如某天下午兩點發現中午十二點有一次誤删表,需要找回資料,那你可以這麼做:

  • 首先,找到最近的一次全量備份,如果你運氣好,可能就是昨天晚上的一個備份,從這個備份恢複到臨時庫;
  • 然後,從備份的時間點開始,将備份的binlog依次取出來,重放到中午誤删表之前的那個時刻。

這樣你的臨時庫就跟誤删之前的線上庫一樣了,然後你可以把表資料從臨時庫取出來,按需要恢複到線上庫去。

好了,說完了資料恢複過程,我們回來說說,為什麼日志需要“兩階段送出”。這裡不妨用反證法來進行解釋。

由于redo log和binlog是兩個獨立的邏輯,如果不用兩階段送出,要麼就是先寫完redo log再寫binlog,或者采用反過來的順序。我們看看這兩種方式會有什麼問題。

仍然用前面的update語句來做例子。假設目前ID=2的行,字段c的值是0,再假設執行update語句過程中在寫完第一個日志後,第二個日志還沒有寫完期間發生了crash,會出現什麼情況呢?

  1. 先寫redo log後寫binlog。假設在redo log寫完,binlog還沒有寫完的時候,MySQL程序異常重新開機。由于我們前面說過的,redo log寫完之後,系統即使崩潰,仍然能夠把資料恢複回來,是以恢複後這一行c的值是1。

    但是由于binlog沒寫完就crash了,這時候binlog裡面就沒有記錄這個語句。是以,之後備份日志的時候,存起來的binlog裡面就沒有這條語句。

    然後你會發現,如果需要用這個binlog來恢複臨時庫的話,由于這個語句的binlog丢失,這個臨時庫就會少了這一次更新,恢複出來的這一行c的值就是0,與原庫的值不同。

  2. 先寫binlog後寫redo log。如果在binlog寫完之後crash,由于redo log還沒寫,崩潰恢複以後這個事務無效,是以這一行c的值是0。但是binlog裡面已經記錄了“把c從0改成1”這個日志。是以,在之後用binlog來恢複的時候就多了一個事務出來,恢複出來的這一行c的值就是1,與原庫的值不同。

    可以看到,如果不使用“兩階段送出”,那麼資料庫的狀态就有可能和用它的日志恢複出來的庫的狀态不一緻。

你可能會說,這個機率是不是很低,平時也沒有什麼動不動就需要恢複臨時庫的場景呀?

其實不是的,不隻是誤操作後需要用這個過程來恢複資料。當你需要擴容的時候,也就是需要再多搭建一些備庫來增加系統的讀能力的時候,現在常見的做法也是用全量備份加上應用binlog來實作的,這個“不一緻”就會導緻你的線上出現主從資料庫不一緻的情況。

簡單說,redo log和binlog都可以用于表示事務的送出狀态,而兩階段送出就是讓這兩個狀态保持邏輯上的一緻。

3.3. 建議:

  1. redo log用于保證

    crash-safe

    能力。

    innodb_flush_log_at_trx_commit

    這個參數設定成1的時候,表示每次事務的redo log都直接持久化到磁盤。這個參數我建議你設定成1,這樣可以保證MySQL異常重新開機之後資料不丢失。
  2. sync_binlog

    這個參數設定成1的時候,表示每次事務的binlog都持久化到磁盤。這個參數我也建議你設定成1,這樣可以保證MySQL異常重新開機之後binlog不丢失。

我還跟你介紹了與MySQL日志系統密切相關的“兩階段送出”。兩階段送出是跨系統維持資料邏輯一緻性時常用的一個方案,即使你不做資料庫核心開發,日常開發中也有可能會用到。

3.4. 學習收獲

redo是實體的,binlog是邏輯的;現在由于redo是屬于InnoDB引擎,是以必須要有binlog,因為你可以使用别的引擎

保證資料庫的一緻性,必須要保證2份日志一緻,使用的2階段式送出;其實感覺像事務,不是成功就是失敗,不能讓中間環節出現,也就是一個成功,一個失敗

如果有一天mysql隻有InnoDB引擎了,有redo來實作複制,那麼感覺oracle的DG就誕生了,實體的速度也将遠超邏輯的,畢竟隻記錄了改動向量

binlog幾大模式,一般采用row,因為遇到時間,從庫可能會出現不一緻的情況,但是row更新前後都有,會導緻日志變大

最後2個參數,保證事務成功,日志必須落盤,這樣,資料庫crash後,就不會丢失某個事務的資料了

其次說一下,對問題的了解

備份時間周期的長短,感覺有2個友善

首先,是恢複資料丢失的時間,既然需要恢複,肯定是資料丢失了。如果一天一備份的話,隻要找到這天的全備,加入這天某段時間的binlog來恢複,如果一周一備份,假設是周一,而你要恢複的資料是周日某個時間點,那就,需要全備+周一到周日某個時間點的全部binlog用來恢複,時間相比前者需要增加很多;看業務能忍受的程度

其次,是資料庫丢失,如果一周一備份的話,需要確定整個一周的binlog都完好無損,否則将無法恢複;而一天一備,隻要保證這天的binlog都完好無損;當然這個可以通過校驗,或者備援等技術來實作,相比之下,上面那點更重要

4. 事務復原日志 undolog

4.1. undolog 介紹

  1. undo: 意味撤銷或取消, 以撤銷操作為目的, 傳回指定某個狀态的操作
  2. undolog: 資料庫事務開始之前, 會将修改記錄存放到undolog中, 當事務復原時,或資料庫崩潰時, 可以利用undolog, 撤銷未送出事務對資料庫産生的影響.
  3. undolog産生時間: undolog在事務開始前産生;
  4. undolog銷毀時間: 事務在送出時, 并不會立刻删除undolog, 因為這個過程中可能需要用到undolog, 比如MVCC多版本控制; InnoDB會将該事務對應的undolog入到删除清單中, 後面會通過**背景線程

    purge thread

    **進行回收處理.
  5. undolog相當于邏輯日志, 記錄的是變化過程, 比如做一個删除

    delete

    , undolog記錄

    insert

    , 反言之, 做

    insert

    操作, undolog記錄

    delete

    , 這樣在出問題時, 就可以直接運作undolog復原到起始位置
  6. undolog存儲: undolog采用段方式管理和記錄, 在InnoDB資料檔案中包含一種

    rollback segment

    復原段, 内部包含1024個

    undolog segment

  7. 檢視底層檔案:

    show variables like '%innodb_undo%'

4.2. undolog 作用

  1. 就是把所有沒有

    COMMIT

    的事務復原到事務開始之前的狀态
  2. 為了實作事務的原子性: 事務處理過程中, 一旦出錯, 或者使用者執行

    rollback

    語句, 利用undolog備份将資料恢複到事務開始之前狀态
  3. 多版本并發控制(MVCC): Undolog在MySQL InnoDB存儲引擎中, 用來實作多版本并發控制, 事務未送出前, UndoLog儲存了未送出前的版本資料, undolog中資料可作為資料舊版本快照供其他并發事務進行快照讀
    mysql深入淺出之日志(redolog, binlog, undolog)1. 重做日志 redo log 2. 歸檔日志 binlog3. redolog和binlog的差別4. 事務復原日志 undolog5. 總結

    事務A手動開啟事務, 執行更新, 首先會把更新命中的資料備份到undo Buffer中

    事務B手動開啟事務, 執行查詢操作, 會讀取Undolog日志資料傳回, 進行快照讀

5. 總結

redolog日志是用來實作事務持久性, 防止發生故障時間點, 尚有髒頁未寫入表的IBD檔案中, 再重新開機mysql服務, 根據redolog進行重做, 進而實作事務的未寫入磁盤資料進行持久化的特性, 這個特性是系統啟動後自動識别并進行處理的;

binlog日志用來解決删庫跑路的問題, 是手動恢複的;

undolog日志是多版本控制的MVCC底層實作, 根據不同的隔離級别, 進行callback的一個邏輯實作;