1. 程式人生 > 其它 >MySQL常用備份工具流程解析

MySQL常用備份工具流程解析

作為DBA都清楚,資料庫備份是至關重要的,可以說是拯救資料庫最後的靈丹妙藥。所以生產系統的資料一定要有備份,當然備份工具和策略的選擇也十分重要,直接影響到恢復時的效率。

下面我們就看一下常見的備份工具,以及目前最流行的Percona XtraBackup的備份流程。

一 備份工具介紹

1.1 常見備份工具介紹

MySQL常見的備份工具主要分為三種:

  • 邏輯備份,包含 mysqldump和Mydumper;
  • 物理備份,包含 Mysqlbackup和Percona XtraBackup;
  • binlog備份,包含mysqlbinlog。
    這裡先說一下binlog備份,它只是把binlog又複製了一份,並且需要在邏輯備份或者物理備份的基礎上才能進行資料恢復,無法單獨進行資料恢復。

(1)邏輯備份:mysqldump

mysqldump備份出的檔案就是sql檔案,其核心就是對每個表執行select,然後轉化成相應的insert語句。mysqldump的備份流程大致如下:

  1. 對某個庫下所有表加讀鎖;
  2. 迴圈備份備份表資料;
  3. 釋放讀鎖;
  4. 迴圈上面三個步驟;
  5. 備份完畢。

從上面可以看出在mysqldump備份期間,備份到某個資料庫時,該資料庫下的表都會處於只讀狀態,無法對錶進行任何變更,直到該庫下的表備份完畢,這對於線上環境一般是無法接受的。若是指定了--master-data或者 --dump-slave 則會在備份開始時加全域性讀鎖(FLUSH TABLES WITH READ LOCK),直到備份結束。當然我們可以選一個從庫進行備份,這樣就不會影響線上業務。另外使用mysqldump備份還有一個最大的好處,因為備份出來的是sql語句,所以它支援跨平臺和跨版本的資料遷移或者恢復,這是物理備份無法做到的。

但是也正是因為mysqldump備份出來的是sql語句,在使用時要更加註意,否則可能會釀成大禍。例如,使用mysqldump常見的問題有:

  • 本來遷移部分資料到新例項,結果把原有資料刪除了;
  • 由於時區問題,發現恢復出來的表和時間相關的資料不對;
  • 在主庫匯入備份資料後,發現從庫沒有同步;
  • 由於字符集問題,發現恢復後資料出現了亂碼;
  • ...

所以使用mysqldump時一定要了解各個選項的作用,以及確認備份出來的sql檔案裡會有什麼操作,會對現有資料造成什麼影響。

Mydumper原理與Mysqldump原理類似,最大的區別是引入了多執行緒備份,每個備份執行緒備份一部分表,當然併發粒度可以到行級,達到多執行緒備份的目的。這裡不再單獨介紹。

(2)物理備份:Percona XtraBackup

Percona XtraBackup是 Percona 公司開發的一個用於 MySQL 資料庫物理熱備的備份工具,是基於 InnoDB 的崩潰恢復功能來實現的。它的基本工作原理如下:

  1. 在啟動時建立一個redo log拷貝程序,獲取並記錄當前的日誌序列號 (LSN),從該位點開始持續拷貝有變化的redo log;
  2.  開啟idb檔案拷貝執行緒,拷貝 ibdata1,undo tablespaces及所有的ibd檔案;
  3. ibd檔案拷貝結束,通知呼叫FTWRL(或加備份鎖);
  4.  備份非 InnoDB 資料(.frm、.MRG、.MYD、.MYI ...... 等檔案);
  5. 備份slave和binlog相關資訊;
  6. 重新整理日誌,拷貝最新的redo log完成後退出日誌拷貝執行緒;
  7. 釋放全域性鎖,記錄備份元資料等,備份結束。

Percona XtraBackup在進行恢復時會應用拷貝的redo log,應用已提交的事務,回滾未提交的事物,將資料庫恢復到一致性狀態。因為Percona XtraBackup備份出來的是物理檔案,所以在使用備份出的檔案進行恢復或者遷移時,不會像mysqldump那樣會存在很多問題。

使用XtraBackup備份時根據備份引數設定不同,對資料庫的變更會造成不同程度的影響,具體影響會在下文分析。

(3)備份工具對比

  mysqldump XtraBackup
庫級別備份
表級別備份
備份部分記錄 -
增量備份 - ● 
流式備份 -
是否鎖表 -
跨版本遷移 -
恢復耗時 較長 較短

通過對比發現,XtraBackup具有對資料庫影響小,且能快速恢復的優點,在日常備份中是首選;mysqldump使用相對更加靈活,但是使用是要注意對資料庫原有資料的影響。

1.2 備份策略

備份策略主要有:全量備份和增量備份,再加上binlog備份。

 二 XtraBackup備份流程解析

Percona XtraBackup 是目前備份MySQL使用最廣泛的工具。在備份過程中,資料庫可以進行正常的讀寫或者其他變更操作,但是偶爾也會遇見備份引起的元資料鎖,或提交事務時發現被binlog lock阻塞等情況。下面我們就看一下Percona XtraBackup的備份流程和加鎖時機。

說明:以下對Percona XtraBackup 的分析都是基於2.4.23的版本,其他版本會略有差別,但是關鍵步驟基本相同。

2.1 拷貝redo日誌

XtraBackup在備份開始時,會建立一個後臺執行緒,專門用於拷貝資料庫的redo log。首先XtraBackup會掃描每組redo log的頭部,找出當前的checkpoint lsn,然後從該lsn後順序拷貝所有的redo log,包括後續新產生的redo log。該執行緒會一直持續到將非事務表完全拷貝完成,才會安全退出。備份日誌輸出中會記錄拷貝開始時的checkpoint lsn。日誌輸出如下:

1 InnoDB: Number of pools: 1
2 # 下面的3028328就是開始拷貝的lsn
3 211214 09:56:09 >> log scanned up to (3028328)
4 InnoDB: Opened 4 undo tablespaces
5 InnoDB: 0 undo tablespaces made active
6 xtrabackup: Generating a list of tablespaces

2.2 拷貝ibd檔案

在拷貝ibd檔案之前,會先掃描資料庫的資料檔案目錄,獲取ibdata1,undo tablespaces及所有的ibd檔案列表,並會記錄相應的 space id,因為在恢復時需要這些 space id來找到對應 doublewrite buffer裡頁面的內容,以及對應的redo log條目。然後開始迴圈拷貝ibdata1,undo tablespaces及所有的ibd檔案。
這裡可通過設定--parallel進行多執行緒備份,提高物理檔案的拷貝效率。不設定則預設為1。

2.3 拷貝非ibd檔案

在所有ibd檔案拷貝完成後,XtraBackup開始備份非ibd檔案。這一部分的邏輯比較複雜,因為備份非ibd檔案前需要加鎖,具體是否會加鎖主要受到--no-lock 引數設定的影響。

2.3.1 no-lock 選項引數說明

若是設定了--no-lock為TRUE,則不會使用"FLUSH TABLES WITH READ LOCK"去加全域性讀鎖,但是若備份過程中對non-InnoDB表執行了DDL或者DML操作, 這會導致備份的不一致,恢復出來的資料就會有問題。所以是不建議將--no-lock為TRUE,預設值是FALSE,也就是在不指定該選項的情況下會在備份非ibd檔案前加全域性讀鎖。

下面我們結合原始碼來看看判斷是否加全域性鎖這部分的具體流程邏輯:

 1 /* 備份非ibd檔案函式入口 */
 2 backup_start()
 3 {
 4 /* opt_no_lock指的是--no-lock引數 */
 5 if (!opt_no_lock) {
 6 /* 如果設定--safe-slave-backup為TRUE,則會會執行"STOP SLAVE SQL_THREAD"關閉SQL執行緒,
 7 並等待Slave_open_temp_tables變數為0。*/
 8 if (opt_safe_slave_backup) {
 9 if (!wait_for_safe_slave(mysql_connection)) {
10 return(false);
11 }
12 }
13 /* 呼叫backup_files函式備份非ibd檔案,加了全域性讀鎖還會呼叫一次。
14 這一次,實際上針對的是--rsync方式 */
15 if (!backup_files(fil_path_to_mysql_datadir, true)) {
16 return(false);
17 }
18 
19 history_lock_time = time(NULL);
20 /* 加全域性讀鎖 */
21 if (!lock_tables_maybe(mysql_connection,
22 opt_backup_lock_timeout,
23 opt_backup_lock_retry_count)) {
24 return(false);
25 }
26 }
27 /* 備份非ibd檔案 */
28 if (!backup_files(fil_path_to_mysql_datadir, false)) {
29 return(false);
30 }
31 
32 ...
33 ...


流程圖如下:

總結來看:

1)若--no-lock為FALSE(預設值),則先施加全域性讀鎖,然後再進行拷貝檔案,另外若 --safe-slave-backup 設定為TRUE ,則會在加全域性鎖之前關閉SQL_THREAD執行緒;

2)若--no-lock為TRUE,則不會施加鎖,直接進行拷貝檔案。

2.3.2 加鎖處理邏輯

加鎖的邏輯主要由lock_tables_maybe實現,先看一下lock_tables_maybe原始碼,如下:

 1 /* 加全域性鎖程式入口 */
 2 lock_tables_maybe(MYSQL *connection, int timeout, int retry_count)
 3 {
 4 /* 若已經執行了 LOCK TABLES FOR BACKUP / FLUSH TABLES WITH READ LOCK ,
 5 或者指定了 --lock-ddl-per-table 選項,返回成功,*/
 6 if (tables_locked || opt_lock_ddl_per_table) {
 7 return(true);
 8 }
 9 /* 若支援備份鎖則執行 "LOCK TABLES FOR BACKUP" 加鎖 */
10 if (have_backup_locks) {
11 return lock_tables_for_backup(connection, timeout, retry_count);
12 }
13 
14 /* 設定加鎖超時時間 */
15 if (have_lock_wait_timeout) {
16 char query[200];
17 
18 ut_snprintf(query, sizeof(query),
19 "SET SESSION lock_wait_timeout=%d", timeout);
20 
21 xb_mysql_query(connection, query, false);
22 }
23 /* flush tables */
24 if (!opt_lock_wait_timeout && !opt_kill_long_queries_timeout) {
25 msg_ts("Executing FLUSH NO_WRITE_TO_BINLOG TABLES...\n");
26 xb_mysql_query(connection,
27 "FLUSH NO_WRITE_TO_BINLOG TABLES", false);
28 }
29 /* 判斷等待加鎖時間是否超時,若超時返回FALSE。 */
30 if (opt_lock_wait_timeout) {
31 if (!wait_for_no_updates(connection, opt_lock_wait_timeout,
32 opt_lock_wait_threshold)) {
33 return(false);
34 }
35 }
36 
37 msg_ts("Executing FLUSH TABLES WITH READ LOCK...\n");
38 /* 若等待了 kill-long-queries-timeout 引數設定的時間後,阻塞加鎖的事務還沒結束,xtrbackup會將其kill */
39 if (opt_kill_long_queries_timeout) {
40 start_query_killer();
41 }
42 /* 判斷galera-info選項設定 */
43 if (have_galera_enabled) {
44 xb_mysql_query(connection,
45 "SET SESSION wsrep_causal_reads=0", false);
46 }
47 /* 加全域性讀鎖 */
48 xb_mysql_query(connection, "FLUSH TABLES WITH READ LOCK", false);
49 
50 if (opt_kill_long_queries_timeout) {
51 stop_query_killer();
52 }
53 
54 tables_locked = true;
55 
56 return(true);
57 }


lock_tables_maybe函式簡化處理流程如下:

1)若備份例項上已經加鎖( LOCK TABLES FOR BACKUP / FLUSH TABLES WITH READ LOCK)或者設定lock-ddl-per-table 則直接返回;
2)若支援備份鎖,則執行LOCK TABLES FOR BACKUP;
3)若不支援備份鎖,則執行 FLUSH TABLES WITH READ LOCK。根據相應選項設定,在執行該操作前會判斷是否有執行中的DDL/DML,以及等待超時時間,是否kill 對應的未結束的事務等。

從上文中我們還看到一個引數--safe-slave-backup ,該引數的主要作用是:
若是在從庫執行的備份操作時設定了該引數,可以防止因從庫同步主庫操作,而導致XtraBackup長時間請求不到鎖而造成備份失敗。
--safe-slave-backup選項說明如下:

  若是設定了 --safe-slave-backup 為TRUE,那麼會執行"STOP SLAVE SQL_THREAD",並等待Slave_open_temp_tables 為零才開始拷貝非ibd檔案,Slave_open_temp_tables 為零說明SQL thread執行的事務都已經完成,這樣就能保證備份的一致性。並且此時也不會有在執行的事務阻塞XtraBackup施加全域性鎖。

2.4 備份slave和binlog資訊

備份完非ibd檔案後,將會備份slave和binlog資訊。
1. 如果命令列中指定了 --slave-info ,則會執行 SHOW SLAVE STATUS 獲取複製的相關資訊並記錄到xtrabackup_slave_info檔案中,主要包含從庫同步到的主庫的binlog位點(Relay_Master_Log_File,Exec_Master_Log_Pos)或者GTID值(Executed_Gtid_Set)等。下面是基於GTID複製備份時xtrabackup_slave_info檔案記錄的複製相關資訊:

1 SET GLOBAL gtid_purged='6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83, 9841546e-15f0-11ec-9557-fa163e736db4:1';
2 CHANGE MASTER TO MASTER_AUTO_POSITION=1

2. 如果命令列中指定了 --binlog-info ,則會執行 SHOW MASTER STATUS 獲取 Binlog 的位置點資訊,並記錄到xtrabackup_binlog_info檔案中。主要資訊包含 當前的binlog檔名,binlog位點以及當前的GTID。binlog-info無需顯式指定,因為它的預設值為AUTO,如果開啟了Binlog,則為ON。xtrabackup_binlog_info檔案內容如下:

mysql-bin.000004 2004 6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83,9841546e-15f0-11ec-9557-fa163e736db4:1

需要注意,在支援備份鎖的例項上備份,指定了 --slave-info 或--binlog-info 均會先施加binlog備份鎖( LOCK BINLOG FOR BACKUP),這會阻塞任何會更改binlog 位點的操作。

2.5 備份結束

備份完資料庫的所有檔案和binlog等相關資訊,備份工作就基本完成了,之後主要執行的操作如下:
1)執行"FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS",將所有的redo log刷盤;
2)停止redo log複製執行緒
3)釋放全域性讀鎖(備份鎖),binlog鎖;
4)開啟SQL_THREAD;
5)拷貝ib_buffer_pool和ib_lru_dump檔案;
6)生成配置檔案backup-my.cnf;
7)列印備份資訊到xtrabackup_info檔案,這些資訊主要包含備份時使用的引數資訊,備份起止時間,binlog位點資訊,以及將會回到的lsn點。
下面是xtrabackup_info記錄的部分內容:

uuid = 057349b8-5c81-11ec-9595-fa163e736db4
name =
tool_name = xtrabackup
tool_command = --defaults-file=/home/3308/etc/my.cnf --user=ivan --password=... --host=127.0.0.1 --port=3308 --backup --binlog-info=ON --target-dir=/home/backup
tool_version = 2.4.7
ibbackup_version = 2.4.7
server_version = 5.7.26-29-log
#備份開始時間
start_time = 2021-12-14 09:56:09
#備份結束時間
end_time = 2021-12-14 09:56:15
lock_time = 0
#binlog,GTID資訊
binlog_pos = filename 'mysql-bin.000004', position '71128', GTID of the last change '6b7bda9f-15f0-11ec-ba14-fa163ea367a4:1-83,
9841546e-15f0-11ec-9557-fa163e736db4:1-232'
innodb_from_lsn = 0
# 恢復時會恢復到該lsn的狀態
innodb_to_lsn = 2941006
partial = N
incremental = N
format = file
compact = N
compressed = N
encrypted = N

三 Percona XtraBackup和鎖相關的引數與操作

3.1 相關引數

  • --lock-ddl:若在支援備份鎖的例項上指定該選項,則會在備份開始時執行"LOCK TABLES FOR BACKUP",以阻止所有的DDL操作。加鎖時間是在拷貝redo log的執行緒建立前,並持續加鎖到dump buffer pool前釋放鎖。看一下相關的日誌:

  

# 執行LOCK TABLES FOR BACKUP 施加備份鎖,阻止DDL操作
18:00:33 Executing LOCK TABLES FOR BACKUP...
xtrabackup version 2.4.23 based on MySQL server 5.7.34 Linux (x86_64)

  • --lock-ddl-per-table:  在備份開始時對每個innodb表施加元資料鎖,防止其上的DDL操作。加鎖時間也是在拷貝redo log的執行緒建立前,持續到所有備份工作完成後才釋放鎖。看一下相關的備份日誌:
# 開始對所有innodb表施加元資料鎖
17:17:21 Initializing MDL on all current tables.
17:17:21 Locking MDL for `mysql`.`engine_cost`
...
17:17:21 Locking MDL for `test`.`promotion_poi`
17:17:21 >> log scanned up to (17361493)
xtrabackup: Generating a list of tablespaces
## 開始備份innodb表
...
...
## 開始備份非innodb表
# 釋放元資料鎖
17:17:41 Unlocking MDL for all tables

17:17:41 completed OK!

加鎖對應的函式是mdl_lock_tables,釋放鎖對應的函式是mdl_unlock_all,主要是執行COMMIT,結束mdl_lock_tables中開啟的顯式事務,來釋放MDL鎖。

mdl_lock_tables流程如下:

  • --no-lock 詳見 2.3.1 節內容


上面引數--lock-ddl和--lock-ddl-per-table是在Percona XtraBackup 2.4.8 之後新增的,因為MySQL 5.7新增了一個叫做 Sorted Index Builds 的功能,這會導致某些DDL操作不記錄重做日誌而導致備份失敗。使用--lock-ddl或--lock-ddl-per-table就會在備份開始時施加鎖,阻止DDL操作。
另外,若備份時指定了--lock-ddl或--lock-ddl-per-table,則在備份非ibd檔案時就不是再有加鎖操作。

3.2 資料庫中執行的加鎖操作

  • LOCK TABLES FOR BACKUP:使用新的MDL型別鎖,用於阻止所有非事務表的DML,以及所有型別表的DDL操作,但是不影響無鎖情況的select操作和對事務表的DML操作。在LOCK TABLES FOR BACKUP 下執行DDL或者對非事務表執行DML,則會被堵塞,執行show processlist 可以看到被阻塞執行緒狀態為Waiting for backup lock。
  • LOCK BINLOG FOR BACKUP:使用的是另一種新的MDL型別鎖,用於阻止所有可能更改二進位制日誌位置或 Exec_Master_Log_Pos 或 Exec_Gtid_Set的操作。在 LOCK BINLOG FOR BACKUP下執行任何會更改 binlog 位點的操作都會被阻塞,執行show processlist 可以看到被阻塞執行緒狀態為Waiting for binlog lock。
  • FLUSH TABLES WITH READ LOCK簡稱(FTWRL):關閉所有開啟的表並使用全域性讀鎖鎖定所有資料庫的所有表,這時資料庫處於**只讀狀態**,任何DDL/DML操作都會被阻塞。執行show processlist 可以看到被阻塞執行緒狀態為Waiting for global read lock。另外,由於FTWRL需要關閉表,如有大查詢,會導致FTWRL等待,進而導致DML/DDL堵塞的時間變長。即使是備庫,也有SQL執行緒在複製來源於主庫的更新,上全域性鎖時,會導致主備庫延遲。

注意, LOCK TABLES FOR BACKUP和LOCK BINLOG FOR BACKUP 語句只有在支援備份鎖的例項上才會執行,Percona Server for MySQL已經在5.6.16-64.0版本開始支援這種更加輕量的備份鎖。

四 思考

Q1: 使用XtraBackup備份的檔案進行恢復時,恢復到哪個時間點?
A:恢復到執行LOCK BINLOG FOR BACKUP或FLUSH TABLES WITH READ LOCK的時間點,因為這時任何改變binlog位點的操作都會被阻塞,redo log和binlog 是一致的。

Q2: 在開啟binlog的情況下,MySQL的奔潰恢復是同時依賴binlog和redo log這兩種日誌的,為什麼XtraBackup 不用備份binlog?
A:因為在備份中有執行LOCK BINLOG FOR BACKUP/FLUSH TABLES WITH READ LOCK,阻止了任何改變binlog位點的操作,這樣只需要根據redo log將有commit log 的事務提交,沒有commit log的事務進行回滾即可。

Q3: 使用Percona XtraBackup備份完成後redo的位點是和binlog是一樣還是比binlog多一些?

A:通過分析備份流程可以發現備份binlog位點資訊(加binlog鎖)是發生在停止redo拷貝執行緒前,而釋放鎖是在停止redo拷貝線之後,所以redo log會多一些。鎖住了binlog保證了在該binlog位點前已經提交的事務的redo log都有commit log的資訊,未提交的事物也就沒有對應的commit log的資訊,即便在鎖住binlog後有Innodb表新的DML產生的redo log,但是事務無法提交,也就沒有commit log的資訊的,最後在回放的過程中對沒有commit log的事務進行回滾就可以了。

Q4:Percona XtraBackup什麼時候會加鎖,以及影響加鎖時間長度的因素有哪些?
A:上面進行了分析,加鎖操作只在備份非ibd檔案時執行,加鎖時長主要和非事務表的數量和大小有關,非事務表的數量越多,體積越大,拷貝檔案所用的時間越長,那麼加鎖時間也就越長。也會和redo log生成的速度有關,只是redo log刷盤受到多個因素的影響,未及時刷盤的redo log一般很小。

Q5:Percona XtraBackup 和mysqldump選擇哪個更好?

A:通過上面的的解析,若是整個例項備份,首先選擇Percona XtraBackup,因為對資料庫的影響最小。若只是備份某個庫表,這個就要視資料量而定,若資料量不大可以使用mysqldump。注意,對資料庫做備份時最好選擇業務連線最少的從庫,因為備份也會消耗一定的資源,避免影響業務,