【轉】mysqldump與innobackupex知多少


作者:羅小波

【目錄】

1. 先看mysqldump 
1.1 mysqldump備份過程解讀 
1.2 mysqldump備份過程中的關鍵步驟 
1.2.1 FLUSH TABLES和FLUSH TABLES WITH READ LOCK的區別 
1.2.2 修改隔離級別的作用 
1.2.3 使用WITH CONSISTENT SNAPSHOT子句的作用 
1.2.4 使用savepoint來設置回滾點的作用 
1.3 mysqldump有什么坑嗎? 
1.3.1. 坑一 
1.3.2. 坑二 
1.3.3. 有辦法改善這這些問題嗎? 
2. 現在看innobackupex 
2.1 innobackupex備份過程解讀 
2.2 innobackupex為什么需要這么做 
2.3 innobackupex有什么坑嗎? 
3. 總結

導讀

想必搞數據庫的都知道:

  • mysqldump優點:mysqldump的優點就是邏輯備份,把數據生成SQL形式保存,在單庫,單表數據遷移,備份恢復等場景方便,SQL形式的備份文件通用,也方便在不同數據庫之間移植。對於InnoDB表可以在線備份。

  • mysqldump缺點:mysqldump是單線程,數據量大的時候,備份時間長,甚至有可能在備份過程中非事務表長期鎖表對業務造成影響(SQL形式的備份恢復時間也比較長)。mysqldump備份時會查詢所有的數據,這可能會把內存中的熱點數據刷掉

  • innobackupex優點:物理備份可以繞過MySQL Server層,加上本身就是文件系統級別的備份,備份速度塊,恢復速度快,可以在線備份,支持並發備份,支持加密傳輸,支持備份限速

  • innobackupex缺點:要提取部分庫表數據比較麻煩,不能按照基於時間點來恢復數據,並且不能遠程備份,只能本地備份,增量備份的恢復也比較麻煩。如果使用innobackupex的全備+binlog增量備份就可以解決基於時間點恢復的問題。

要查看備份過程中這倆備份工具都對數據庫做了什么操作,想必大家都知道:可以打開general_log來查。那么問題來了,general_log輸出的信息都代表什么?如果不這樣做會怎樣?這兩個備份工具會不會有什么平時被忽略的坑?請看下文分析,也許……你會發現原來之前對這倆備份工具好像也不是那么了解!

環境信息

  • 服務器配置:

    • CPU:4 vcpus
    • 內存:4G
    • 磁盤:250G SAS
    • 網卡:Speed: 1000Mb/s
  • 操作系統:CentOS release 6.5 (Final)

  • 數據庫版本:MySQL 5.7.17

  • xtrabackup版本:2.4.4

  • 主從IP(文中一些演示步驟需要用到主備復制架構):

    • 主庫:192.168.2.111(以下稱為A庫)
    • 從庫:192.168.2.121(以下稱為B庫)
  • 數據庫關鍵配置參數

    • 主庫:雙一log_slave_updateslog-binbinlog_rows_query_log_events=ONserver-id=3306111gtid_mode=ONenforce_gtid_consistency=ONauto_increment_increment=2auto_increment_offset=1
    • 備庫:雙一log_slave_updateslog-binbinlog_rows_query_log_events=ONserver-id=3306121gtid_mode=ONenforce_gtid_consistency=ONauto_increment_increment=2auto_increment_offset=2
  • 測試庫表創建(這里在同一個庫下創建兩個表,一個表為InnoDB引擎,一個為MyISAM引擎)

root@localhost : (none) 04:21:27> create database luoxiaobo;
Query OK, 1 row affected (0.01 sec)
root@localhost : (none) 04:21:45> use luoxiaobo
Database changed
root@localhost : luoxiaobo 04:21:55> create table t_luoxiaobo(id int unsigned not null primary key auto_increment,test varchar(50),datet_time datetime) engine=innodb;
Query OK, 0 rows affected (0.05 sec)
root@localhost : luoxiaobo 04:23:00> insert into t_luoxiaobo(test,datet_time) values('1',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:32> insert into t_luoxiaobo(test,datet_time) values('2',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 04:23:36> insert into t_luoxiaobo(test,datet_time) values('3',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:38> insert into t_luoxiaobo(test,datet_time) values('4',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 04:23:41> select * from t_luoxiaobo;
+----+------+---------------------+
| id | test | datet_time          |
+----+------+---------------------+
|  1 | 1    | 2017-07-01 16:23:32 |
|  3 | 2    | 2017-07-01 16:23:36 |
|  5 | 3    | 2017-07-01 16:23:38 |
|  7 | 4    | 2017-07-01 16:23:41 |
+----+------+---------------------+
4 rows in set (0.00 sec)
root@localhost : luoxiaobo 04:24:51> create table t_luoxiaobo2(id int unsigned not null primary key auto_increment,test varchar(50),datet_time datetime) engine=myisam;
Query OK, 0 rows affected (0.04 sec)
root@localhost : luoxiaobo 05:38:19> insert into t_luoxiaobo2(test,datet_time) values('1',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 05:38:29> insert into t_luoxiaobo2(test,datet_time) values('2',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 05:38:32> insert into t_luoxiaobo2(test,datet_time) values('3',now());
Query OK, 1 row affected (0.01 sec)
root@localhost : luoxiaobo 05:38:35> insert into t_luoxiaobo2(test,datet_time) values('4',now());
Query OK, 1 row affected (0.00 sec)
root@localhost : luoxiaobo 05:38:37> select * from t_luoxiaobo2;
+----+------+---------------------+
| id | test | datet_time          |
+----+------+---------------------+
|  1 | 1    | 2017-07-01 17:38:29 |
|  3 | 2    | 2017-07-01 17:38:32 |
|  5 | 3    | 2017-07-01 17:38:35 |
|  7 | 4    | 2017-07-01 17:38:37 |
+----+------+---------------------+
4 rows in set (0.00 sec)

1. 先看mysqldump

1.1 mysqldump備份過程解讀

通常,使用mysqldump備份期間,為了使得數據庫中加鎖時間盡量短,會使用--single-transaction選項來開啟一個一致性快照事務,為了使得備份期間能夠獲得一個與數據一致的binlog pos點,會使用--master-data選項,現在登錄A庫主機,使用這倆選項執行備份演示。

  • 先在數據庫中打開general_log
root@localhost : luoxiaobo 04:23:50> show variables like 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | OFF   |
+---------------+-------+
1 row in set (0.00 sec)
root@localhost : luoxiaobo 04:24:41> set global general_log=1;
Query OK, 0 rows affected (0.03 sec)
root@localhost : luoxiaobo 04:24:49> show variables like 'general_log';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| general_log   | ON    |
+---------------+-------+
1 row in set (0.01 sec)
  • 使用mysqldump備份(使用strace捕獲執行過程中的調用棧),這里緊以備份測試庫luoxiaobo為例進行演示:
[root@localhost ~]# strace mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events luoxiaobo > \
backup_`date +%F_%H_%M_%S`.sql 2> strace_mysqldump.txt 
  • 備份完成之后,查看general_log中的內容(去掉了一些無用信息):
* 留意unlock tables語句的位置,是在show master status語句獲取了binlog pos之后立即執行
[root@localhost ~]# cat /home/mysql/data/mysqldata1/mydata/localhost.log 
......
' #修改session級別的sql_mode為空,避免可能有些sql_mode值對備份產生影響'
2017-07-01T17:42:17.779564+08:00        6 Query /*!40100 SET @@SQL_MODE='' */  2017-07-01T17:42:17.779695+08:00        6 Query /*!40103 SET TIME_ZONE='+00:00' */
' #強制刷新表緩存到磁盤並關閉表(但已經加表鎖的表會阻塞該語句)'
2017-07-01T17:42:17.779889+08:00        6 Query FLUSH /*!40101 LOCAL */ TABLES 
' # 對整個實例加全局讀鎖,如果存在表鎖將阻塞加全局讀鎖語句'
2017-07-01T17:42:17.780047+08:00        6 Query FLUSH TABLES WITH READ LOCK
' #在session級別修改隔離級別為RR'
2017-07-01T17:42:17.780201+08:00        6 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
'# 開啟一個一致性快照事務,必須在隔離級別RR下才能開啟一個快照事務'
2017-07-01T17:42:17.780326+08:00        6 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ 
'#查看是否開啟GTID'
2017-07-01T17:42:17.780452+08:00        6 Query SHOW VARIABLES LIKE 'gtid\_mode' 
'#如果開啟GTID則查看當前的事務GTID集合'
2017-07-01T17:42:17.781867+08:00        6 Query SELECT @@GLOBAL.GTID_EXECUTED 
'#查看當前數據的binlog pos'
2017-07-01T17:42:17.781999+08:00        6 Query SHOW MASTER STATUS  
'#釋放全局讀鎖,留意解鎖的位置,下文會專門提到這個'
2017-07-01T17:42:17.782113+08:00        6 Query UNLOCK TABLES 
......
2017-07-01T17:42:17.786315+08:00        6 Init DB   luoxiaobo
'#在一個數據庫開始備份之前,設置一個保存點(回滾點)'
2017-07-01T17:42:17.786428+08:00        6 Query SAVEPOINT sp  
'#查看庫下有哪些表'
2017-07-01T17:42:17.786539+08:00        6 Query show tables  
' #查看這個表的狀態'
2017-07-01T17:42:17.786710+08:00        6 Query show table status like 't\_luoxiaobo' 
'# 給每個表的每個字段加個反引號'
2017-07-01T17:42:17.786908+08:00        6 Query SET SQL_QUOTE_SHOW_CREATE=1 
'#表結構的備份都是binary格式,所以要先改這個'
2017-07-01T17:42:17.787023+08:00        6 Query SET SESSION character_set_results = 'binary' 
' #查看這個表的定義語句'
2017-07-01T17:42:17.787137+08:00        6 Query show create table `t_luoxiaobo` 
'# 修改session的數據結果返回字符集,備份數據需要使用數據原本的字符集,這里是utf8'
2017-07-01T17:42:17.787329+08:00        6 Query SET SESSION character_set_results = 'utf8' 
' #查看這個表的字段信息'
2017-07-01T17:42:17.787450+08:00        6 Query show fields from `t_luoxiaobo` 
2017-07-01T17:42:17.787715+08:00        6 Query show fields from `t_luoxiaobo`
' #查詢表中的數據,結合show fields from `t_luoxiaobo`的字段信息生成insert into語句'
2017-07-01T17:42:17.787967+08:00        6 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo` 
2017-07-01T17:42:17.788285+08:00        6 Query SET SESSION character_set_results = 'binary' 
2017-07-01T17:42:17.788411+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.788535+08:00        6 Query select @@collation_database
'#查看是否有這個表的觸發器'
2017-07-01T17:42:17.788668+08:00        6 Query SHOW TRIGGERS LIKE 't\_luoxiaobo'  
2017-07-01T17:42:17.788926+08:00        6 Query SET SESSION character_set_results = 'utf8'
' #t_luoxiaobob表備份結束,回滾到保存點sp,以釋放select *...語句產生的MDL鎖,如果不回滾到sp,后續整個備份過程中無法對該表執行DDL操作'
2017-07-01T17:42:17.789043+08:00        6 Query ROLLBACK TO SAVEPOINT sp  
2017-07-01T17:42:17.789191+08:00        6 Query show table status like 't\_luoxiaobo2'
2017-07-01T17:42:17.789399+08:00        6 Query SET SQL_QUOTE_SHOW_CREATE=1
2017-07-01T17:42:17.789510+08:00        6 Query SET SESSION character_set_results = 'binary'
2017-07-01T17:42:17.789625+08:00        6 Query show create table `t_luoxiaobo2`
2017-07-01T17:42:17.789753+08:00        6 Query SET SESSION character_set_results = 'utf8'
2017-07-01T17:42:17.789871+08:00        6 Query show fields from `t_luoxiaobo2`
2017-07-01T17:42:17.790123+08:00        6 Query show fields from `t_luoxiaobo2`
'#備份表t_luoxiaobo2'
2017-07-01T17:42:17.790486+08:00        6 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2`  
2017-07-01T17:42:17.790689+08:00        6 Query SET SESSION character_set_results = 'binary'
2017-07-01T17:42:17.790806+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.790923+08:00        6 Query select @@collation_database
2017-07-01T17:42:17.791053+08:00        6 Query SHOW TRIGGERS LIKE 't\_luoxiaobo2'
2017-07-01T17:42:17.791378+08:00        6 Query SET SESSION character_set_results = 'utf8'
'#備份t_luoxiaobo2表過程與t__luoxiaobo表完全一樣'
2017-07-01T17:42:17.791497+08:00        6 Query ROLLBACK TO SAVEPOINT sp  
'#整個luoxiaobo庫備份完成之后,釋放該保存點'
2017-07-01T17:42:17.791606+08:00        6 Query RELEASE SAVEPOINT sp  
' #查看是否有相關的events'
2017-07-01T17:42:17.791717+08:00        6 Query show events 
2017-07-01T17:42:17.792065+08:00        6 Query use `luoxiaobo`
2017-07-01T17:42:17.792323+08:00        6 Query select @@collation_database
2017-07-01T17:42:17.792489+08:00        6 Query SET SESSION character_set_results = 'binary'
'#查看luoxiaobo庫是否有存儲函數'
2017-07-01T17:42:17.792617+08:00        6 Query SHOW FUNCTION STATUS WHERE Db = 'luoxiaobo' 
' #查看luoxiaobo庫是否有存儲過程'
2017-07-01T17:42:17.793967+08:00        6 Query SHOW PROCEDURE STATUS WHERE Db = 'luoxiaobo'
2017-07-01T17:42:17.794952+08:00        6 Query SET SESSION character_set_results = 'utf8'
'#備份結束,退出連接'
2017-07-01T17:42:17.805746+08:00        6 Quit    

查看strace抓取的調用棧信息,限於篇幅,詳見為知筆記鏈接:

http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac3oxBB40tGQNY2L6Z_M2LtLbG

上面的strace信息是不是看起來和general_log中的信息很像啊?因為general_log中記錄的就是mysqldump發送過去的SQL語句:

從上面general_log和strace信息對比我們可以知道,strace信息代表了mysqldump進程對數據庫進程發送了哪些請求信息,general_log代表了數據庫中所有的客戶端SQL請求操作記錄,這就是大家熟知的mysqldump備份過程中的關鍵步驟,那么問題來了,mysqldump備份過程中為什么需要這些 步驟?不這么做會怎樣?下面對這些步驟逐一使用演示步驟進行詳細解釋。

1.2 mysqldump備份過程中的關鍵步驟

1.2.1 FLUSH TABLES和FLUSH TABLES WITH READ LOCK的區別

  • FLUSH TABLES

強制關閉所有正在使用的表,並刷新查詢緩存,從查詢緩存中刪除所有查詢緩存結果,類似RESET QUERY CACHE語句的行為

在MySQL 5.7官方文檔描述中,當有表正處於LOCK TABLES … READ語句加鎖狀態時,不允許使用FLUSH TABLES語句(另外一個會話執行FLUSH TABLES會被阻塞),如果已經使用LOCK TABLES … READ語句對某表加讀鎖的情況下要對另外的表執行刷新,可以在另外一個會話中使用FLUSH TABLES tbl_name … WITH READ LOCK語句(稍后會講到)

注意:

  • 如果一個會話中使用LOCK TABLES語句對某表加了表鎖,在該表鎖未釋放前,那么另外一個會話如果執行FLUSH TABLES語句會被阻塞
  • 如果一個會話正在執行DDL語句,那么另外一個會話如果執行FLUSH TABLES 語句會被阻塞
  • 如果一個會話正在執行DML大事務(DML語句正在執行,數據正在發生修改,而不是使用lock in share mode和for update語句來顯式加鎖),那么另外一個會話如果執行FLUSH TABLES語句會被阻塞
  • FLUSH TABLES WITH READ LOCK

關閉所有打開的表,並使用全局讀鎖鎖定整個實例下的所有表。此時,你可以方便地使用支持快照的文件系統進行快照備份,備份完成之后,使用UNLOCK TABLES語句釋放鎖。

FLUSH TABLES WITH READ LOCK語句獲取的是一個全局讀鎖,而不是表鎖,因此表現行為不會像LOCK TABLES和UNLOCK TABLES語句,LOCK TABLES和UNLOCK TABLES語句在與事務混搭時,會出現一些相互影響的情況,如下:

  • 如果有表使用了LOCK TABLES語句加鎖,那么開啟一個事務會造成該表的表鎖被釋放(注意是任何表的表鎖,只要存在表鎖都會被釋放,另外,必須是同一個會話中操作才會造成這個現象),就類似執行了UNLOCK TABLES語句一樣,但使用FLUSH TABLES WITH READ LOCK語句加全局讀鎖,開啟一個事務不會造成全局讀鎖被釋放

  • 如果你開啟了一個事務,然后在事務內使用LOCK TABLES語句加鎖和FLUSH TABLES WITH READ LOCK語句加全局讀鎖(注意,是對任何表加表鎖,只要使用了LOCK TABLES),會造成該事務隱式提交

  • 如果你開啟了一個事務,然后在事務內使用UNLOCK TABLES語句,無效

  • 官方文檔中還有一句:”如果有表使用LOCK TABLES語句加表鎖,在使用UNLOCK TABLES語句解鎖時會造成該表的所有事務隱式提交”,個人認為這是理論上的說法,或者說本人能力有限,暫未想到可能會造成這種情況的原因,因為實際上使用LOCK TABLES語句語句時,開啟一個事務會造成自動解鎖(前面已經提到過),而如果在事務內使用LOCK TABLES語句會造成事務隱式提交(前面已經提到過),所以實際上不可能出現在事務內使用UNLOCK TABLES語句解鎖LOCK TABLES語句的情況,而如果是使用FLUSH TABLES WITH READ LOCK語句,如果執行該語句之前存在LOCK TABLES加的表鎖,則FLUSH TABLES WITH READ LOCK語句發生阻塞,如果是已經執行FLUSH TABLES WITH READ LOCK語句,LOCK TABLES語句發生阻塞,不會再有任何的表鎖和互斥鎖能夠被獲取到(新的非select和show的請求都會被阻塞)。所以不可能出現UNLOCK TABLES語句解鎖時造成隱式提交

注:

  • FLUSH TABLES WITH READ LOCK語句不會阻塞日志表的寫入,例如:查詢日志,慢查詢日志等
  • FLUSH TABLES WITH READ LOCK語句與XA協議不兼容
  • 如果一個會話中使用LOCK TABLES語句對某表加了表鎖,在該表鎖未釋放前,那么另外一個會話如果執行FLUSH TABLES WITH READ LOCK語句會被阻塞,而如果數據庫中lock_wait_timeout參數設置時間太短,mysqldump將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出
  • 如果一個會話正在執行DDL語句,那么另外一個會話如果執行FLUSH TABLES WITH READ LOCK語句會被阻塞,如果數據庫中lock_wait_timeout參數設置時間太短,mysqldump將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出
  • 如果一個會話正在執行DML大事務(DML語句正在執行,數據正在發生修改,而不是使用lock in share mode和for update語句來顯式加鎖),那么另外一個會話如果執行FLUSH TABLES WITH READ LOCK語句會被阻塞,如果數據庫中lock_wait_timeout參數設置時間太短,mysqldump將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出
  • FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK

刷新表並獲取指定表的讀鎖。該語句首先獲取表的獨占MDL鎖,所以需要等待該表的所有事務提交完成。然后刷新該表的表緩存,重新打開表,獲取表讀鎖(類似LOCK TABLES … READ),並將MDL鎖從獨占級別降級為共享。在該語句獲取表讀鎖、降級MDL鎖之后,其他會話可以讀取該表,但不能修改表數據及其表結構。

執行該語句需要RELOAD和LOCK TABLES權限。

該語句僅適用於基表(持久表),不適用於臨時表,會自動忽略,另外在對視圖使用該語句使會報錯。

與LOCK TABLES語句類似,在使用該語句對某表加鎖之后,再同一個會話中開啟一個事務時,會被自動解鎖

MySQL5.7官方文檔描述說:這種新的變體語法能夠使得只針對某一個表加讀鎖的同時還能夠同時刷新這個表,這解決了某表使用LOCK TABLES … READ語句加讀鎖時,需要刷新表不能使用FLUSH TABLES語句的問題,此時可以使用FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK語句代替,但是,官方描述不太清晰,實測在同一個會話中使用LOCK TABLES … READ語句加讀鎖時,不允許執行該語句(無論操作表是否是同一張表),會報錯:ERROR 1192 (HY000): Can’t execute the given command because you have active locked tables or an active transaction,但是如果在不同的會話中,那么,如果表不相同,允許執行,表相同,則FLUSH TABLES tbl_name [,tbl_name] … WITH READ LOCK語句發生等待

該語句同一個會話重復執行時,無論是否同一個表,都會報錯:ERROR 1192 (HY000): Can't execute the given command because you have active locked tables or an active transactio,如果是不同會話不同表則允許執行,但是表相同則發生等待

1.2.2 修改隔離級別的作用

為什么要執行SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ語句呢?因為后續需要使用START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT語句開啟一個一致性事務快照,根據事務一致性讀要求,一致性事務快照只支持RR隔離級別,在其他隔離級別下執行語句START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT會報如下警告信息:

'# RU、RC、串行隔離級別報一樣的警告,告訴你WITH CONSISTENT SNAPSHOT子句被忽略,該子句只支持RR隔離級別'
root@localhost : (none) 02:54:15> show variables like '%isolation%';
+---------------+----------------+
| Variable_name | Value          |
+---------------+----------------+
| tx_isolation  | READ-COMMITTED |
+---------------+----------------+
1 row in set (0.00 sec)
root@localhost : (none) 02:54:35> START TRANSACTION WITH CONSISTENT SNAPSHOT;
Query OK, 0 rows affected, 1 warning (0.00 sec)
Warning (Code 138): InnoDB: WITH CONSISTENT SNAPSHOT was ignored because this phrase can only be used with REPEATABLE READ isolation level.

1.2.3 使用WITH CONSISTENT SNAPSHOT子句的作用

START TRANSACTION語句使用WITH CONSISTENT SNAPSHOT子句時,會為事務啟動一致性讀(該子句僅適用於InnoDB)。其行為與執行START TRANSACTION語句之后+一個SELECT語句效果相同(會獲取一個事務號,在read view中占個坑,但是不會請求任何鎖)。WITH CONSISTENT SNAPSHOT子句不會自動修改當前的事務隔離級別,由於WITH CONSISTENT SNAPSHOT子句要求必須RR隔離級別下才會自動啟用,因此只有當前隔離級別為RR時才會啟用一致性快照,非RR隔離級別下,會忽略WITH CONSISTENT SNAPSHOT子句。從MySQL 5.7.2起,當WITH CONSISTENT SNAPSHOT子句被忽略時,會產生一個警告(類似上一篇mysqldump與innobackupex備份過程你知多少(一)提到的警告信息)。

為了使得更清晰地了解mysqldump在備份過程中使用WITH CONSISTENT SNAPSHOT子句的作用,下面咱們來演示一下帶與不帶WITH CONSISTENT SNAPSHOT子句會發生什么?

  • 開啟兩個會話,操作同一張表






從上面的表格對比結果中可以看到:

  • WITH CONSISTENT SNAPSHOT子句的作用就相當於START TRANSACTION+ SELECT語句,目地是為了在開啟事務的那一刻往mvcc的read view中立即加入這個事務,就好像read view在事務一開始就被固定了一樣,使得后續其他事務的DML不會影響到該事務的查詢結果,這就是所說的一致性讀

  • 如果不使用WITH CONSISTENT SNAPSHOT子句,在使用START TRANSACTION語句顯式開啟一個事務之后,在執行SELECT語句之前,這段時間內如果有別的事務發起了DML操作,就會導致該事務查詢該表的時候讀取的數據與事務開始時間點不一致。

1.2.4 使用savepoint來設置回滾點的作用

大家都知道,設置SAVEPOINT是為了回滾在設置這個點時候發生變更的數據,但是mysqldump備份只是使用select語句做查詢,為什么要使用savepoint呢?需要回滾什么呢?請看下文分析:

  • SAVEPOINT 'identifier'語句,為事務設置一個命名的事務保存點(回滾點),該字符串為事務保存點的標識符。

  • ROLLBACK TO SAVEPOINT語句的作用是將事務回滾到指定的保存點的位置,而不終止事務。當前事務在回滾點之后的修改的行數據將被撤銷(注:InnoDB不會釋放這些發生修改且被撤銷行的行鎖,注意是修改,不是新插入,這些發生修改的數據行行鎖被存儲在內存中),對於設置了保存點之后,新插入的行數據也會被撤銷(注:這些鎖信息被存儲在行數據中的事務ID上,這些行鎖不會單獨存儲在內存中,在這種情況下,這些新插入的行數據在被回滾之后,對應的行鎖將被釋放)。另外,回滾到某個保存點之后,比這個保存點在時間上更晚設置的保存點將被刪除。

  • ROLLBACK TO SAVEPOINT語句還有一個作用,可以釋放在設置保存點之后事務持有的MDL鎖,這點便是mysqldump需要使用保存點的關鍵點。

為了更清晰地了解mysqldump在備份過程中使用SAVEPOINT sp + ROLLBACK TO SAVEPOINT sp語句的作用,下面使用兩個會話演示一下使用與不使用保存點會發生什么?






從上面的對比結果中可以得知:

  • mysqldump使用savepoint的作用就是,當一個顯式開啟的事務回滾到保存點時,除了回滾數據變更之外,還會釋放保存點之后select語句獲取的MDL鎖,使得其他會話的DDL語句可以正常執行。對於mysqldump來說,select 語句執行完成之后就代表着該表的數據已經備份完成,無需再繼續持有MDL鎖,使用savepoint就實現了在select 執行完成之后釋放MDL鎖的目的(注:在事務內,執行select *語句雖然不會有數據行鎖,但是會持有表的MDL鎖)。

  • with consistent snapshot子句對應mysqldump實現一致性備份來說至關重要,不僅僅是數據的一致性,使用該子句時,表定義也保持事務開啟的那一刻,所以,從上面的對比結果中可以看到,使用了with consistent snapshot子句開啟一個一致性快照事務之后,如果一旦表結構定義發生改變,事務將無法重復查詢表。

  • 從上面的演示過程中,我們也可以看到,使用 with consistent snapshot子句顯式開啟一個事務之后,如果該事務沒有對任何表做任何操作時,此時是沒有獲得任何鎖的,所以,如果在該事務對某表執行操作之前其他事務對該表執行了DDL操作之后,將導致該事務無法再對表執行查詢,會報表結構發生變化的錯誤;當然,如果顯式開啟事務后立即對某表執行查詢,那么其他會話的DDL是會發生阻塞的;當在該事務使用savepoint實現方式釋放表的MDL鎖之后,其他會話允許執行DDL,但是執行了DDL語句之后,該事務就無法再對該表執行查詢。當然,如果不使用 with consistent snapshot子句,則其他會話執行的DDL對表定義的變更不會影響到該事務重復對表執行查詢。

1.3 mysqldump有什么坑嗎?

想必大家都知道,mysqldump備份時可以使用--single-transaction + --master-data兩個選項執行備份(老實講,為圖方便,本人之前很長一段時間,生產庫也是使用mysqldudmp遠程備份的),這樣備份過程中既可以盡量不鎖表,也可以獲取到binlog pos位置,備份文件可以用於數據恢復,也可以用於搭建備庫。看起來那么美好,然而……其實一不小心你就發現踩到坑了

1.3.1 坑一

使用--single-transaction + --master-data時,myisam表持續不斷插入,並用於搭建備庫。

首先在A庫上把myisam表的數據行數弄到100W以上:

...... root@localhost : luoxiaobo 11:26:42> insert into t_luoxiaobo2(test,datet_time) select test,now() from t_luoxiaobo2; Query OK, 1572864 rows affected (4.47 sec) Records: 1572864 Duplicates: 0 Warnings: 0 root@localhost : luoxiaobo 11:26:47> select count(*) from t_luoxiaobo2; +----------+ | count(*) | +----------+ | 3145728 | +----------+ 1 row in set (0.00 sec)

A庫新開一個ssh會話2,使用如下腳本持續對表t_luoxiaobo2進行插入操作(該表為myisam表),限於篇幅,請點擊此處獲取。

A庫新開一個ssh會話3,清空查詢日志:

[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 

現在,A庫在ssh會話3中,使用mysqldump備份整個實例:

[root@localhost ~]# mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events -A >\ backup_`date +%F_%H_%M_%S`.sql mysqldump: [Warning] Using a password on the command line interface can be insecure. [root@localhost ~]# ls -lh backup_2017-07-03_00_47_50.sql -rw-r--r-- 1 root root 112M 7月 3 00:47 backup_2017-07-03_00_47_50.sql

備份完成之后,A庫在ssh會話2中,停止持續造數腳本。

A庫在ssh會話2中,查看備份文件中的binlog pos:

[root@localhost ~]# head -100 backup_*.sql |grep -i 'change master to' -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000005', MASTER_LOG_POS=76657819;

A庫在ssh會話3中,查看查詢日志,可以發現在UNLOCK TABLES之后,select *…t_luoxiaobo2表之前,還有數據插入到該表中:

2017-07-03T00:47:50.366670+08:00 87364 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 2017-07-03T00:47:50.366795+08:00 87363 Query insert into t_luoxiaobo2(test,datet_time) values(11377,now()) 2017-07-03T00:47:50.366862+08:00 87364 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ 2017-07-03T00:47:50.367023+08:00 87364 Query SHOW VARIABLES LIKE 'gtid\_mode' 2017-07-03T00:47:50.372331+08:00 87364 Query SELECT @@GLOBAL.GTID_EXECUTED 2017-07-03T00:47:50.372473+08:00 87364 Query SHOW MASTER STATUS 2017-07-03T00:47:50.372557+08:00 87364 Query UNLOCK TABLES ...... 2017-07-03T00:47:50.381256+08:00 87366 Query insert into t_luoxiaobo2(test,datet_time) values(11383,now()) ...... 2017-07-03T00:47:50.381577+08:00 87365 Query insert into t_luoxiaobo2(test,datet_time) values(11380,now()) 2017-07-03T00:47:50.381817+08:00 87360 Init DB luoxiaobo 2017-07-03T00:47:50.381886+08:00 87360 Query insert into t_luoxiaobo2(test,datet_time) values(11382,now()) ...... 2017-07-03T00:47:50.391873+08:00 87364 Query show fields from `t_luoxiaobo2` 2017-07-03T00:47:50.392116+08:00 87364 Query show fields from `t_luoxiaobo2` 2017-07-03T00:47:50.392339+08:00 87364 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2`

現在,我們將這個備份文件用於B庫上搭建備庫,並啟動復制,可以發現有如下復制報錯:

root@localhost : (none) 12:59:12> show slave status\G; *************************** 1\. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.2.111 Master_User: qfsys Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000005 Read_Master_Log_Pos: 79521301 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 320 Relay_Master_Log_File: mysql-bin.000005 Slave_IO_Running: Yes Slave_SQL_Running: No ...... Last_SQL_Errno: 1062 Last_SQL_Error: Could not execute Write_rows event on table luoxiaobo.t_luoxiaobo2; Duplicate entry '6465261' for key 'PRIMARY', Error_code: 1062;\ handler error HA_ERR_FOUND_DUPP_KEY; the event's master log mysql-bin.000005, end_log_pos 76658175 ...... ERROR: No query specified

從上面的結果中可以看到,主鍵沖突了,也就是說備份的表t_luoxiaobo2中的數據與備份文件中獲取的binlog pos點並不一致,咱們現在在B庫中,查詢一下這個表中大於等於這個沖突主鍵的數據,從下面的結果中可以看到,備份文件中如果嚴格按照一致性要求,備份文件中的數據必須和binlog pos點一致,但是現在,備份文件中的數據卻比獲取的binlog pos點多了5行數據:

root@localhost : (none) 12:59:24> use luoxiaobo Database changed root@localhost : luoxiaobo 12:59:44> select id from t_luoxiaobo2 where id>=6465261; +---------+ | id | +---------+ | 6465261 | | 6465263 | | 6465265 | | 6465267 | | 6465269 | +---------+ 5 rows in set (0.01 sec)

現在,咱們去掉--single-transaction選項,重新執行本小節以上步驟,重新搭建從庫,看看是否還有問題(這里限於篇幅,步驟省略,只貼出最后結果):

root@localhost : (none) 01:09:12> change master to master_host='192.168.2.111',master_user='qfsys',master_password='letsg0',master_log_file='mysql-bin.000005',\ master_log_pos=83601517; Query OK, 0 rows affected, 2 warnings (0.02 sec) Note (Code 1759): Sending passwords in plain text without SSL/TLS is extremely insecure. Note (Code 1760): Storing MySQL user name or password information in the master info repository is not secure and is therefore not recommended. Please consider \ using the USER and PASSWORD connection options for START SLAVE; see the 'START SLAVE Syntax' in the MySQL Manual for more information. root@localhost : (none) 01:09:23> start slave; Query OK, 0 rows affected (0.01 sec) root@localhost : (none) 01:09:25> show slave status\G; *************************** 1\. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.2.111 Master_User: qfsys Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000005 Read_Master_Log_Pos: 84697699 Relay_Log_File: mysql-relay-bin.000002 Relay_Log_Pos: 1096502 Relay_Master_Log_File: mysql-bin.000005 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 84697699 ......

從上面的show slave status輸出信息中我們可以看到,去掉了--single-transaction選項之后的備份,用於搭建備庫就正常了。另外,我們重新在A庫上查看查詢日志也可以發現,只搜索到flush語句而沒有搜索到unlock tables、set session transaction.. 、start transaction.. 語句,說明備份過程沒有開啟一致性快照事務,沒有修改隔離級別,是全程加全局讀鎖的,mysqldump備份進程結束退出之后mysql server自動回收鎖資源:

[root@localhost ~]# grep -iE 'flush|unlock tables|transaction' /home/mysql/data/mysqldata1/mydata/localhost.log 2017-07-03T01:07:08.195470+08:00 102945 Query FLUSH /*!40101 LOCAL */ TABLES 2017-07-03T01:07:08.206607+08:00 102945 Query FLUSH TABLES WITH READ LOCK

也許你會說,我們數據庫環境很規范,沒有myisam表,不會有這個問題,OK,贊一個。

1.3.2 坑二

使用--single-transaction + --master-data時,InnoDB表執行online ddl,備份文件用於搭建備庫(注意,本小節中的數據庫實例與前一小節不同)。

這次我們操作InnoDB表,在A庫上先把t_luoxiaobo表的數據也弄到幾百萬行。

qogir_env@localhost : luoxiaobo 10:03:35> insert into t_luoxiaobo(test,datet_time) select test,now() from t_luoxiaobo; Query OK, 1048576 rows affected (9.83 sec) Records: 1048576 Duplicates: 0 Warnings: 0 qogir_env@localhost : luoxiaobo 10:03:46> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 2097152 | +----------+ 1 row in set (0.39 sec)

A庫在ssh會話2中,使用如下腳本持續對表t_luoxiaobo進行DDL操作(該表為InnoDB表),限於篇幅,請點擊此處獲取。

A庫在ssh會話3中,清空查詢日志:

[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 

現在,A庫在ssh會話3中,使用mysqldump備份整個實例:

[root@localhost ~]# mysqldump -h 192.168.2.111 -uadmin -pletsg0 --single-transaction --master-data=2 --triggers --routines --events -A > backup_`date +%F_%H_%M_%S`.sql mysqldump: [Warning] Using a password on the command line interface can be insecure. [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# ls -lh backup_2017-07-03_12_46_49.sql -rw-r--r-- 1 root root 74M Jul 3 12:46 backup_2017-07-03_12_46_49.sql

A庫在ssh會話2中,停止DDL添加腳本。

A庫在ssh會話2中,查看備份文件中的binlog pos:

[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# head -100 backup_*.sql |grep -i 'change master to' -- CHANGE MASTER TO MASTER_LOG_FILE='mysql-bin.000257', MASTER_LOG_POS=62109599;

現在,我們將這個備份文件用於在B庫中搭建備庫,並啟動復制,從下面的結果中可以看到,復制狀態正常:

qogir_env@localhost : (none) 01:32:00> show slave status\G; ...... Master_Log_File: mysql-bin.000257 Read_Master_Log_Pos: 62110423 Relay_Log_File: mysql-relay-bin-channel@002d241.000002 Relay_Log_Pos: 1144 Relay_Master_Log_File: mysql-bin.000257 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 62110423 ...... Seconds_Behind_Master: 0 ...... Retrieved_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:53831090-53831093 Executed_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:1-53831093, f9b1a9b6-46b7-11e7-9e8b-00163e4fde29:1 Auto_Position: 0 ......

現在我們回到A庫上,對表t_luoxiaobo插入一些測試數據:

qogir_env@localhost : luoxiaobo 12:43:30> insert into t_luoxiaobo(test,datet_time) values('test_replication',now()); Query OK, 1 row affected (0.00 sec) qogir_env@localhost : luoxiaobo 01:36:31> select * from t_luoxiaobo where test='test_replication'; +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | id | test | datet_time | test1 | test2 | test3 | test4 | test5 | test6 | test8 | test7 | test9 | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 7470943 | test_replication | 2017-07-03 13:36:31 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 1 row in set (0.96 sec)

在B庫上查詢復制狀態和表t_luoxiaobo中的數據:

qogir_env@localhost : luoxiaobo 01:32:21> show slave status\G; ...... Master_Log_File: mysql-bin.000257 Read_Master_Log_Pos: 62110862 Relay_Log_File: mysql-relay-bin-channel@002d241.000002 Relay_Log_Pos: 1583 Relay_Master_Log_File: mysql-bin.000257 Slave_IO_Running: Yes Slave_SQL_Running: Yes ...... Exec_Master_Log_Pos: 62110862 ...... Seconds_Behind_Master: 0 ...... Retrieved_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:53831090-53831094 Executed_Gtid_Set: 799ef59c-4126-11e7-83ce-00163e407cfb:1-53831094, f9b1a9b6-46b7-11e7-9e8b-00163e4fde29:1 ...... 1 row in set (0.00 sec) qogir_env@localhost : luoxiaobo 01:38:23> select * from t_luoxiaobo where test='test_replication'; +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | id | test | datet_time | test1 | test2 | test3 | test4 | test5 | test6 | test8 | test7 | test9 | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ | 7470943 | test_replication | 2017-07-03 13:36:31 | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | NULL | +---------+------------------+---------------------+-------+-------+-------+-------+-------+-------+-------+-------+-------+ 1 row in set (0.05 sec)

到這里,看起來一切正常,對不對?開心嗎?先等等,請保持DBA一貫嚴謹的優良傳統,咱們在主庫上使用pt-table-checksum工具檢查一下:

# 刪除了一部分無用信息,只保留了我們之前造數的兩張表 [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# pt-table-checksum --nocheck-replication-filters --no-check-binlog-format --replicate=xiaoboluo.checksums h=localhost,u=admin,p=letsg0,P=3306 TS ERRORS DIFFS ROWS CHUNKS SKIPPED TIME TABLE 07-03T13:57:48 0 16 2097153 18 0 7.543 luoxiaobo.t_luoxiaobo 07-03T13:57:57 0 0 2097152 18 0 9.281 luoxiaobo.t_luoxiaobo2 ......

從上面的信息中可以看到,表luoxiaobo.t_luoxiaobo的檢測DIFFS 列為16,代表主從有數據差異,神馬情況?別急,咱們先來分別在AB庫查詢下這張表的數據行數,從下面的結果可以看到,該表主從數據差異2097152行!!

# A庫 qogir_env@localhost : (none) 01:57:03> use luoxiaobo Database changed qogir_env@localhost : luoxiaobo 02:09:40> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 2097153 | +----------+ 1 row in set (0.41 sec) B庫 qogir_env@localhost : (none) 01:55:28> use luoxiaobo Database changed qogir_env@localhost : luoxiaobo 02:10:30> select count(*) from t_luoxiaobo; +----------+ | count(*) | +----------+ | 1 | +----------+ 1 row in set (0.00 sec)

發生什么了?也許你會說,平時使用mysqldump不都是這樣的嗎?沒毛病啊。回想一下,從咱們上篇《mysqldump與innobackupex備份過程知多少(二)》中提到的“WITH CONSISTENT SNAPSHOT語句的作用”時的演示過程可以知道,DDL的負載是刻意加上去的,還記得之前演示mysqldump使用savepoint的作用的時候,使用start transaction with consistent snapshot語句顯式開啟一個事務之后,該事務執行select之前,該表被其他會話執行了DDL之后無法查詢數據,我們知道mysqldump備份數據的時候,就是在start transaction with consistent snapshot語句開啟的一個一致性快照事務下使用select語句查詢數據進行備份的。為了證實這個問題,下面我們打開查詢日志查看一下在start transaction with consistent snapshot語句和select … 之間是否有DDL語句,如下:

.......

2017-07-03T12:46:57.082727+08:00 1649664 Query SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ 2017-07-03T12:46:57.082889+08:00 1649664 Query START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */ ...... 2017-07-03T12:46:57.085821+08:00 1649664 Query SELECT @@GLOBAL.GTID_EXECUTED 2017-07-03T12:46:57.085954+08:00 1649664 Query SHOW MASTER STATUS ...... ' # 這里可以看到,在START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */語句之后,select 備份表t_luoxiaobo表之前,有一個DDL語句插入進來' 2017-07-03T12:46:57.089833+08:00 1649667 Query alter table t_luoxiaobo add column test8 varchar(10) 2017-07-03T12:46:57.095153+08:00 1649664 Query UNLOCK TABLES ...... 2017-07-03T12:46:57.098199+08:00 1649664 Init DB luoxiaobo 2017-07-03T12:46:57.098273+08:00 1649664 Query SHOW CREATE DATABASE IF NOT EXISTS `luoxiaobo` 2017-07-03T12:46:57.098360+08:00 1649664 Query SAVEPOINT sp 2017-07-03T12:46:57.098435+08:00 1649664 Query show tables 2017-07-03T12:46:57.098645+08:00 1649664 Query show table status like 't\_luoxiaobo' 2017-07-03T12:46:57.098843+08:00 1649664 Query SET SQL_QUOTE_SHOW_CREATE=1 2017-07-03T12:46:57.098927+08:00 1649664 Query SET SESSION character_set_results = 'binary' 2017-07-03T12:46:57.099013+08:00 1649664 Query show create table `t_luoxiaobo` 2017-07-03T12:46:57.105056+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.105165+08:00 1649664 Query show fields from `t_luoxiaobo` 2017-07-03T12:46:57.105538+08:00 1649664 Query show fields from `t_luoxiaobo` '# 這里原本應該是一句:SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo`,但是卻沒有,我們可以推理一下,因為select的時候報了表定'\ '# 義已經發生變化的錯誤,所以這句select並沒有被記錄到查詢日志中來' 2017-07-03T12:46:57.105857+08:00 1649664 Query SET SESSION character_set_results = 'binary' 2017-07-03T12:46:57.105929+08:00 1649664 Query use `luoxiaobo` 2017-07-03T12:46:57.106021+08:00 1649664 Query select @@collation_database 2017-07-03T12:46:57.106116+08:00 1649664 Query SHOW TRIGGERS LIKE 't\_luoxiaobo' 2017-07-03T12:46:57.106394+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.106466+08:00 1649664 Query ROLLBACK TO SAVEPOINT sp 2017-07-03T12:46:57.106586+08:00 1649664 Query show table status like 't\_luoxiaobo2' ...... 2017-07-03T12:46:57.107063+08:00 1649664 Query show create table `t_luoxiaobo2` 2017-07-03T12:46:57.107151+08:00 1649664 Query SET SESSION character_set_results = 'utf8' 2017-07-03T12:46:57.107233+08:00 1649664 Query show fields from `t_luoxiaobo2` 2017-07-03T12:46:57.107511+08:00 1649664 Query show fields from `t_luoxiaobo2` 2017-07-03T12:46:57.107807+08:00 1649664 Query SELECT /*!40001 SQL_NO_CACHE */ * FROM `t_luoxiaobo2` ......

現在,我們打開備份文件,找到表t_luoxiaob的備份語句位置,可以看到並沒有生成INSERT語句:

DROP TABLE IF EXISTS `t_luoxiaobo`; /*!40101 SET @saved_cs_client = @@character_set_client */; /*!40101 SET character_set_client = utf8 */; CREATE TABLE `t_luoxiaobo` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `test` varchar(50) COLLATE utf8_bin DEFAULT NULL, `datet_time` datetime DEFAULT NULL, `test1` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test2` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test3` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test4` varchar(10) COLLATE utf8_bin DEFAULT NULL, `test5` varchar(10) COLLATE utf8_bin DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=7470943 DEFAULT CHARSET=utf8 COLLATE=utf8_bin; /*!40101 SET character_set_client = @saved_cs_client */; '# 正常情況下,在這個位置,應該出現LOCK TABLES `t_luoxiaobo` WRITE; + /*!40000 ALTER TABLE `t_luoxiaobo2` DISABLE KEYS */; + INSERT INTO語句的,然而,現在這里卻是空的' -- -- Table structure for table `t_luoxiaobo2` -- DROP TABLE IF EXISTS `t_luoxiaobo2`; ...... LOCK TABLES `t_luoxiaobo2` WRITE; /*!40000 ALTER TABLE `t_luoxiaobo2` DISABLE KEYS */; INSERT INTO `t_luoxiaobo2` VALUES (1,'1','2017-07-03 09:22:16'),(4,'2','2017-07-03 09:22:19'),(7,'3','2017-07-03 09:22:21'), ......

到這里,是不是突然心弦一緊呢?so..如果你決定繼續使用mysqldump,那么以后搭建好備庫之后,一定要記得校驗一下主備數據一致性!!

1.3.3 有辦法改善這這些問題嗎?

在尋找解決辦法之前,咱們先來看看mysqldump的備份選項--single-transaction--master-data[=value]的作用和使用限制。

  • --single-transaction

此選項將事務隔離模式設置為REPEATABLE READ,並在備份數據之前向server發送START TRANSACTION SQL語句以顯示開啟一個事務快照。僅適用於InnoDB這樣的事務表,由於是在事務快照內進行備份,這樣可以使得備份的數據與獲取事務快照時的數據是一致的,而且不會阻塞任何應用程序對server的訪問。

在進行單事務備份時,為確保有效的備份文件(正確的表內容和二進制日志位置),不能有其他連接應使用語句:ALTER TABLE,CREATE TABLE,DROP TABLE,RENAME TABLE,TRUNCATE等DDL語句。這會導致一致狀態被破壞,可能導致mysqldump執行SELECT檢索表數據時查詢到不正確的內容或備份失敗 。

注意:該選項僅適用於事務引擎表,對於MyISAM或MEMORY表由於不支持事務,所以備份過程中這些引擎表的數據仍可能發生更改。

  • --master-data[=value]

使用此選項備份時會在備份文件中生成change master to語句,使用的binlog pos是使用的備份server自己的binlog pos,可使用備份文件用於將另一台服務器(恢復這個備份文件的服務器)設置為備份server的從庫。

--dump-slave選項類似,如果選項值為2,則CHANGE MASTER TO語句將作為SQL注釋寫入備份文件,因此僅供參考;當備份文件被重新加載時,這個注釋不起作用。如果選項值為1,則該語句不會注釋,並在重新加載備份文件時會生效(被執行)。如果未指定選項值,則默認值為1。

指定此選項的用戶需要RELOAD權限,並且server必須啟用二進制日志,因為這個位置是使用show master status獲取的(如果沒有開啟log_bin參數,則show master status輸出信息為空),而不是使用show slave status獲取的。

--master-data選項自動關閉--lock-tables選項。同時還會打開--lock-all-tables,除非指定了--single-transaction選項,在指定了--single-transaction選項之后,只有在備份開始時間內才加全局讀取鎖。

so,--single-transaction選項中明確說明了如果使用了該選項,那么在備份期間如果發生DDL,則可能導致備份數據一致性被破壞,select檢索不到正確的內容。另外,該選項僅僅只適用於事務引擎表,不適用於非事務引擎。作為DBA,很多時候是非常無奈的,雖然有各種規范,但是保不齊就是有lo漏網之魚,這個時候,生活還得繼續,工作還得做好, 那么,有什么辦法可以緩解這個問題嗎?有的:

1) 就如同上文中演示步驟中那樣,去掉--single-transaction選項進行備份,此時單獨使用--master-data選項時會自動開啟--lock-all-tables,備份過程中整個實例全程鎖表,不會發生備份數據與獲取的binlog pos點不一致的問題,這樣,用該備份來搭建備庫時就不會出現數據沖突。但是問題顯而易見,備份期間數據庫不可用,如果采用這種方法,至少需要在業務低峰期進行備份
2) 使用innobackupex備份工具

2、現在看innobackupex

2.1. innobackupex備份過程解讀

  • A庫清空查詢日志
[root@localhost ~]# echo > /home/mysql/data/mysqldata1/mydata/localhost.log 
  • 為了更清晰地追蹤innobackupex是如何拷貝redo log的,我們在A庫新開一個ssh會話2,使用如下腳本持續對表t_luoxiaobo進行插入操作(該表為innodb表),限於篇幅,請到如下為知筆記鏈接獲取

  • http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk

  • A庫使用innobackupex執行備份,使用strace命令抓取備份過程中的調用棧

[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# rm -rf /data/backup/full/* '# 注意:strace必須加-f選項,否則fork線程的調用棧打印不出來,因為innobackupex備份時是單進程多線程的的方式執行備份的' [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# strace -f innobackupex --defaults-file=/home/mysql/conf/my1.cnf --user=admin --password=letsg0\ --no-timestamp /data/backup/full 2> a.txt [root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# 
  • 查看general_log日志中的記錄(刪掉了加壓腳本中的語句)
[root@5f1772e3-0c7a-4537-97f9-9b57cf6a04c2 ~]# cat /home/mysql/data/mysqldata1/mydata/localhost.log ...... 2017-07-04T09:58:21.037311Z 6842 Query set autocommit=1 2017-07-04T09:58:21.037916Z 6842 Query SET SESSION wait_timeout=2147483 2017-07-04T09:58:21.038551Z 6842 Query SELECT CONCAT(@@hostname, @@port) 2017-07-04T09:58:21.043617Z 6843 Query SET SESSION wait_timeout=2147483 2017-07-04T09:58:21.043870Z 6843 Query SHOW VARIABLES 2017-07-04T09:58:21.050047Z 6843 Query SHOW ENGINE INNODB STATUS 2017-07-04T09:58:34.949224Z 6843 Query SET SESSION lock_wait_timeout=31536000 2017-07-04T09:58:34.949820Z 6843 Query FLUSH NO_WRITE_TO_BINLOG TABLES 2017-07-04T09:58:34.950331Z 6843 Query FLUSH TABLES WITH READ LOCK 2017-07-04T09:58:38.117764Z 6843 Query SHOW MASTER STATUS 2017-07-04T09:58:38.118012Z 6843 Query SHOW VARIABLES 2017-07-04T09:58:38.123193Z 6843 Query FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS 2017-07-04T09:58:38.327210Z 6843 Query UNLOCK TABLES 2017-07-04T09:58:38.329734Z 6843 Query SELECT UUID() 2017-07-04T09:58:38.330003Z 6843 Query SELECT VERSION() 2017-07-04T09:58:38.539340Z 6843 Quit ...... 
  • 從上面的記錄中可以看到,與mysqldump相比,innobackupex備份時對數據庫的操作多了一個FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,稍后對這句的作用進行解釋

  • 因為innobackupex是物理拷貝文件,數據並不像mysqldump那樣通過對數據庫表執行select語句查詢進行備份,而是通過拷貝磁盤文件進行備份的,所以,主體的備份流程還需要看strace的調用棧,限於篇幅原因,詳見為知筆記外鏈:http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac2ZRJlK3qIAQr2LjYMx2xMkCD

  • 通過備份輸出日志和strace調用棧,整理的流程圖如下(全備)

 

2.2. innobackupex為什么需要這么做

  • innobackupex備份時啟動一個進程多個線程,通過拷貝磁盤文件實現物理備份,為了保證備份數據的一致性,需要在備份過程中恰當的時機發送一些加鎖解鎖語句與數據庫實例進行交互,so...要了解innobackupex工具的整個備份過程中做了哪些事情,我們就需要查看general_log和備份過程中的日志輸出(其實strace調用棧信息里就可以了解到innobackupex所做的所有事情,但是。。都是系統調用,看起來比較費勁),對於備份過程中的日志輸出,這里就不再熬述,詳見上文中的"全備流程圖",本小節我們只介紹general_log中的輸出重點語句,如下:
  • FLUSH NO_WRITE_TO_BINLOG TABLES、FLUSH TABLES WITH READ LOCK、SHOW MASTER STATUS、UNLOCK TABLES幾個語句的作用與mysqldump備份過程中的這幾個語句的作用一樣
  • FLUSH NO_WRITE_TO_BINLOG ENGINE LOGS,該語句在mysqldump備份過程中沒有 * 這句的作用是在所有的事務表和非事務表備份完成,獲取了全局讀鎖,且使用SHOW MASTER STATUS語句獲取了binlog pos之后,執行刷新redo log buffer中的日志到磁盤中,然后redo log copy線程拷貝這最后的redo log日志數據(為什么說是最后的redo log日志數據呢?因為此時使用FLUSH TABLES WITH READ LOCK加鎖之后,使用UNLOCK TABLES釋放全局讀鎖之前,不會再有新的請求進來,),拷貝完成之后就停止copy線程並關閉xtrabackup_logfile文件。然后再使用UNLOCK TABLES釋放全局讀鎖。 * 詳見姜承堯老師的推文:http://chuansong.me/n/372118651979

2.3. innobackupex有什么坑嗎?

  • 從上文中介紹的innobackupex的備份流程和原理上,我們可以得知,innobackupex工具備份過程中是不會出現前面提到的mysqldump備份工具的"坑一"的。因為innobackupex備份工具是在所有事務表和非事務表都備份完成之后才會執行UNLOCK TABLES釋放全局讀鎖,so...從加鎖之后,解鎖之前不可能有任何其他的DML請求能夠對數據做修改,從而保證的備份數據的一致性。

  • 那么,mysqldump的"坑二"呢?我們來看下面請看演示過程

  • A庫使用如下腳本持續對表t_luoxiaobo進行插入操作(該表為innodb表),限於篇幅,請到如下為知筆記鏈接獲取(留意把program_name變量值改為"innobackupex")

  • http://5d096a11.wiz03.com/share/s/1t2mEh0a-kl_2c2NZ33kSiac1Rgvxq1vgkhL21ibWU2cLidk

  • A庫新開一個ssh會話2,執行innobackupex備份,留意日志打印過程。從下面的結果中,我們可以看到報錯終止了

[root@localhost ~]# innobackupex --defaults-file=/home/mysql/conf/my1.cnf --user=admin --password=letsg0 --no-timestamp /data/backup/full ...... 170705 14:47:21 >> log scanned up to (129385440) 170705 14:47:21 [01] ...done 170705 14:47:21 [01] Copying ./luoxiaobo/t_luoxiaobo.ibd to /data/backup/full/luoxiaobo/t_luoxiaobo.ibd #從這里可以看到,正在備份t_luoxiaobo表 170705 14:47:22 >> log scanned up to (129385507) '#這里可以看到,scanned lsn的時候,發現了DDL做的修改,報錯了,告訴你DDL已經執行,將無法保證備份數據的一致性' InnoDB: Last flushed lsn: 129385032 load_index lsn 129385558 [FATAL] InnoDB: An optimized(without redo logging) DDLoperation has been performed. All modified pages may not have been flushed to the disk yet. PXB will not be able take a consistent backup. Retry the backup operation 2017-07-05 14:47:23 0x7f9125365700 InnoDB: Assertion failure in thread 140261371303680 in file ut0ut.cc line 916 InnoDB: We intentionally generate a memory trap. InnoDB: Submit a detailed bug report to http://bugs.mysql.com. InnoDB: If you get repeated assertion failures or crashes, even InnoDB: immediately after the mysqld startup, there may be InnoDB: corruption in the InnoDB tablespace. Please refer to InnoDB: http://dev.mysql.com/doc/refman/5.7/en/forcing-innodb-recovery.html InnoDB: about forcing recovery. 06:47:23 UTC - xtrabackup got signal 6 ; This could be because you hit a bug or data is corrupted. This error can also be caused by malfunctioning hardware. Attempting to collect some information that could help diagnose the problem. As this is a crash and something is definitely wrong, the information collection process might fail. Thread pointer: 0x0 Attempting backtrace. You can use the following information to find out where mysqld died. If you see no messages after this, something went terribly wrong.. ...... 
  • 發生什么了?

  • 首先,我們知道,innobackupex在備份事務表時,是沒有對數據庫加鎖的,so..這個時候,其實DDL是允許執行的,innobackupex持續在備份innodb事務表期間,如果被執行DDL的表是在innobackupex備份完成之后發起,那么在下一次scan lsn的時候innobackupex將發現DDL更改,報錯終止,如果是在備份非事務表期間發起的DDL,那么將被FLUSH TABLE WITH READ LOCK語句阻塞。所以,對於使用innobackupex備份的生產環境,要執行DDL語句,也需要避開備份時間

  • 那么,除了這個,還有其他坑嗎?

  • 前面在介紹mysqldump備份過程中的FLUSH TABLES和FLUSH TABLES WITH READ LOCK語句的時候,提到過三個注意事項,innobackupex備份過程中為了獲得一個一致性備份,仍然會使用這兩個語句對數據庫進行刷新表緩存、加全局讀鎖,也就是說,mysqldump使用這兩個語句可能會踩到的坑,在innobackupex中也會碰到,如下: * 1)、如果一個會話中使用LOCK TABLES語句對某表加了表鎖,在該表鎖未釋放前,那么另外一個會話如果執行FLUSH TABLES和FLUSH TABLES WITH READ LOCK語句會被阻塞,而如果數據庫中lock_wait_timeout參數設置時間太短,innobackupex將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出 * 2)、如果一個會話正在執行DDL語句,那么另外一個會話如果執行FLUSH TABLES和FLUSH TABLES WITH READ LOCK語句會被阻塞,而如果數據庫中lock_wait_timeout參數設置時間太短,innobackupex將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出 * 3)、如果一個會話正在執行DML大事務(DML語句正在執行,數據正在發生修改,而不是使用lock in share mode和for update語句來顯式加鎖),那么另外一個會話如果執行FLUSH TABLES和FLUSH TABLES WITH READ LOCK語句會被阻塞,而如果數據庫中lock_wait_timeout參數設置時間太短,innobackupex將會因為執行FLUSH TABLES WITH READ LOCK語句獲取全局讀鎖超時而導致備份失敗退出

  • 但是,細心的童鞋可能已經發現了,innobackupex備份時的general_log中執行FLUSH NO_WRITE_TO_BINLOG TABLES語句之前,有這樣一句語句:SET SESSION lock_wait_timeout=31536000,備份時會在session級別把鎖超時時間改了,so...除了加表鎖忘記釋放之外,其他兩種情況估計不太可能碰到鎖超時的情況!!

  • 當然,如果每天備份一次,那么我們不太可能讓innobackupex在備份時,獲取全局讀鎖時等待31536000秒,so。。我們可以使用innobackupex的選項--kill-long-queries-timeout,來再獲取全局讀鎖時,如果某查詢阻塞了獲得該FLUSH TABLE WITH READ LOCK語句時間超過這個閥值,那么就對該會話執行kill,殺掉這個連接,當然,你也許會說對數據做修改的不能殺,只能殺查詢的,那么我們可以使用--kill-long-query-type=all|select選項。下面列出這倆選項的含義:

  • --kill-long-query-type=all|select * 該選項指定哪些類型的查詢在指定的查詢時間之后還沒有執行完成時被kill掉,以釋放阻塞加全局讀鎖的鎖,默認值為all,有效值有:all和select * 執行該選項需要有process和super權限

  • --kill-long-queries-timeout=SECONDS * 該選項指定innobackupex在執行FLUSH TABLES WITH READ LOCK時碰到阻塞其獲得鎖的查詢時,等待該參數指定的秒數之后,如果仍然有查詢在運行,則執行kill掉這些查詢 * 默認值為0,表示innobackupex 不啟用嘗試kill掉任何查詢的功能

  • PS:

  • 很多人喜歡在備份前先flush binary logs一把,其實在有大事務對數據進行修改時,一不小心可能就會出現數據庫hang死,所以不建議這么做

  • innobackupex備份期間,在數據庫中創建的連接不要誤殺,否則備份失敗

3、總結

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM