mysqldump備份原理及注意事項

關於MySQL熱備,可分爲兩種方式:

  1. 邏輯備份

  2. 物理備份

對於前者,常用的工具是MySQL自帶的mysqldump,對於後者,常用的工具是Percona提供的XtraBackup。

對於規模比較小,業務並不繁忙的數據庫,一般都是選擇mysqldump。

那麼,mysqldump的備份原理是什麼呢?

拋開源碼不談,其實我們可以通過打開general log,查看mysqldump全庫備份時執行的命令來了解mysqldump背後的原理。

只考慮innodb表的情況如下圖所示:
這裏寫圖片描述

打開general log

root@ 04:55:  [sbtest]> set global general_log=on;
Query OK, 0 rows affected (0.00 sec)

其中,general log的存放路徑可通過以下命令查看

root@ 04:59:  [sbtest]> show variables like '%general_log_file%';
+------------------+---------------------------+
| Variable_name    | Value                     |
+------------------+---------------------------+
| general_log_file | /data/mysql/localhost.log |
+------------------+---------------------------+
1 row in set (0.00 sec)

執行全庫備份

[root@localhost ~]# mysqldump --master-data=2  -R --single-transaction -A -p123456 > lijingkuan.sql
Warning: Using a password on the command line interface can be insecure.
Warning: A partial dump from a server that has GTIDs will by default include the GTIDs of all transactions, even those that changed suppressed parts of the database. If you don't want to restore GTIDs, pass --set-gtid-purged=OFF. To make a complete dump, pass --all-databases --triggers --routines --events. 

其中

–master-data指定爲2指的是會在備份文件中生成CHANGE MASTER的註釋。具體在本例中,指的是

-- CHANGE MASTER TO MASTER_LOG_FILE='mybinlog.000008', MASTER_LOG_POS=222448728;

如果該值設置爲1,則生成的是CHANGE MASTER的命令,而不是註釋。

-R 備份存儲過程與函數

–single-transaction 獲取InnoDB表的一致性備份。

-A 相當於–all-databases。

下面來看看general log中的內容

170529  5:00:47   215 Connect   root@localhost on 
                  215 Query     /*!40100 SET @@SQL_MODE='' */
                  215 Query     /*!40103 SET TIME_ZONE='+00:00' */
                  215 Query     FLUSH /*!40101 LOCAL */ TABLES
                  215 Query     FLUSH TABLES WITH READ LOCK
                  215 Query     SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ
                  215 Query     START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */
                  215 Query     SHOW VARIABLES LIKE 'gtid\_mode'
                  215 Query     SELECT @@GLOBAL.GTID_EXECUTED
                  215 Query     SHOW MASTER STATUS
                  215 Query     UNLOCK TABLES
                  215 Query     SELECT LOGFILE_GROUP_NAME, FILE_NAME, TOTAL_EXTENTS, INITIAL_SIZE, ENGINE, EXTRA FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE = 'UNDO LOG' AND 
FILE_NAME IS NOT NULL GROUP BY LOGFILE_GROUP_NAME, FILE_NAME, ENGINE ORDER BY LOGFILE_GROUP_NAME
                  215 Query     SELECT DISTINCT TABLESPACE_NAME, FILE_NAME, LOGFILE_GROUP_NAME, EXTENT_SIZE, INITIAL_SIZE, ENGINE FROM INFORMATION_SCHEMA.FILES WHERE FILE_TYPE 
= 'DATAFILE' ORDER BY TABLESPACE_NAME, LOGFILE_GROUP_NAME
                  215 Query     SHOW DATABASES
                  215 Query     SHOW VARIABLES LIKE 'ndbinfo\_version'

其中,比較重要的有以下幾點:

1.FLUSH /!40101 LOCAL / TABLES

Closes all open tables, forces all tables in use to be closed, and flushes the query cache.

2.FLUSH TABLES WITH READ LOCK

執行flush tables操作,並加一個全局讀鎖,很多童鞋可能會好奇,這兩個命令貌似是重複的,爲什麼不在第一次執行flush tables操作的時候加上鎖呢?

下面看看源碼中的解釋:

 /*
    We do first a FLUSH TABLES. If a long update is running, the FLUSH TABLES
    will wait but will not stall the whole mysqld, and when the long update is
    done the FLUSH TABLES WITH READ LOCK will start and succeed quickly. So,
    FLUSH TABLES is to lower the probability of a stage where both mysqldump
    and most client connections are stalled. Of course, if a second long
    update starts between the two FLUSHes, we have that bad stall.
  */

簡而言之,是爲了避免較長的事務操作造成FLUSH TABLES WITH READ LOCK操作遲遲得不到鎖,但同時又阻塞了其它客戶端操作。
(flush tables只是關閉所有打開的表,並不獲取鎖。如果沒有長事務,命令會很快執行完成(因爲長事務會導致表無法關閉。但長時間未提交的事務不會導致表無法關閉)。長時間未提交的事務也不會阻塞flush tables。flush tables也不會阻塞後續其他客戶端的事務操作;
flush tables with read lock纔會獲取讀鎖。長時間未提交的事務不會阻塞FTWRL,但是FTWRL會阻塞活動的事務執行後續的更改和提交等操作(比如長時間未提交的事務提交,或者繼續update),以及阻塞後續的事務開始執行,數據庫處於stall狀態;)

3.SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ

設置當前會話的事務隔離等級爲RR,RR可避免不可重複讀和幻讀。

4.START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT /

獲取當前數據庫的快照,這個是由mysqldump中–single-transaction決定的。

這個只適用於支持事務的表,在MySQL中,只有Innodb。
注意:START TRANSACTION和START TRANSACTION WITH CONSISTENT SNAPSHOT並不一樣,

START TRANSACTION WITH CONSISTENT SNAPSHOT是開啓事務的一致性快照。

下面看看官方的說法,

   The WITH CONSISTENT SNAPSHOT modifier starts a consistent read for storage engines that are capable of it. 
   This applies only to InnoDB. 
   The effect is the same as issuing a START TRANSACTION followed by a SELECT from any InnoDB table. 

如何理解呢?

簡而言之,就是開啓事務並對所有表執行了一次SELECT操作,這樣可保證備份時,在任意時間點執行select * from table得到的數據和執行START TRANSACTION WITH CONSISTENT SNAPSHOT時的數據一致。

注意,WITH CONSISTENT SNAPSHOT只在RR隔離級別下有效。

下面通過實例看看START TRANSACTION WITH CONSISTENT SNAPSHOT和START TRANSACTION的不同

注意:session 2是自動提交

START TRANSACTION WITH CONSISTENT SNAPSHOT
這裏寫圖片描述

START TRANSACTION
這裏寫圖片描述

可見,如果僅是START TRANSACTION,事務2的insert操作提交後,session 1可見(注意,可見的前提是session 2的insert操作在session 1的select操作之前)

而如果是START TRANSACTION WITH CONSISTENT SNAPSHOT,則即便session 2的insert操作在session 1的select操作之前,對session 1均不可見。

5.SHOW MASTER STATUS

這個是由–master-data決定的,記錄了開始備份時,binlog的狀態信息,包括MASTER_LOG_FILE和MASTER_LOG_POS

6.UNLOCK TABLES

釋放鎖。

因爲我的數據庫中只有以下五個庫

mysql> show databases;
+--------------------+
| Database           |
+--------------------+
| information_schema |
| mysql              |
| performance_schema |
| sbtest             |
| test               |
+--------------------+

備份的時候可以發現只備份了mysql和test,sbtest,並沒有備份information_schema和performance_schema。

下面來看看備份mysql和test的日誌輸出信息,

因日誌輸出信息太多,在這裏,只選擇sbtest庫的日誌信息。test庫中一共有一張表sbtest。

                  215 Init DB   sbtest
                  215 Query     SHOW CREATE DATABASE IF NOT EXISTS `sbtest`
                  215 Query     SAVEPOINT sp
                  215 Query     show tables
                  215 Query     show table status like 'sbtest'
                  215 Query     SET SQL_QUOTE_SHOW_CREATE=1
                  215 Query     SET SESSION character_set_results = 'binary'
                  215 Query     show create table `sbtest`
                  215 Query     SET SESSION character_set_results = 'utf8'
                  215 Query     show fields from `sbtest`
                  215 Query     SELECT /*!40001 SQL_NO_CACHE */ * FROM `sbtest`
170529  5:00:51   215 Query     SET SESSION character_set_results = 'binary'
                  215 Query     use `sbtest`
                  215 Query     select @@collation_database
                  215 Query     SHOW TRIGGERS LIKE 'sbtest'
                  215 Query     SET SESSION character_set_results = 'utf8'
                  215 Query     ROLLBACK TO SAVEPOINT sp
                  215 Query     RELEASE SAVEPOINT sp
                  215 Query     use `sbtest`
                  215 Query     select @@collation_database
                  215 Query     SET SESSION character_set_results = 'binary'
                  215 Query     SHOW FUNCTION STATUS WHERE Db = 'sbtest'
                  215 Query     SHOW PROCEDURE STATUS WHERE Db = 'sbtest'
                  215 Query     SET SESSION character_set_results = 'utf8'

從上述輸出可以看出:

  1. 備份的核心是SELECT /!40001 SQL_NO_CACHE / * FROM sbtest語句。

    該語句會查詢到表sbtest的所有數據,在備份文件中會生成相應的insert語句。

    其中SQL_NO_CACHE的作用是查詢的結果並不會緩存到查詢緩存中。

  2. SHOW CREATE DATABASE IF NOT EXISTS sbtest,show create table sbtest

    生成創庫語句和創表語句。

  3. SHOW TRIGGERS LIKE ‘sbtest’

    可以看出,如果不加-R參數,默認是會備份觸發器的。

  4. SHOW FUNCTION STATUS WHERE Db = ‘sbtest’
    SHOW PROCEDURE STATUS WHERE Db = ‘sbtest’

    用於備份存儲過程和函數。

  5. 設置SAVEPOINT,然後備份完每個表後再回滾到該SAVEPOINT。

    爲什麼要這麼做呢?

    前面通過START TRANSACTION WITH CONSISTENT SNAPSHOT開啓的事務只能通過commit或者rollback來結束,而不是ROLLBACK TO SAVEPOINT sp。

    其實,這樣做不會阻塞在備份期間對已經備份表的ddl操作。

/**
      ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata
      lock on table which was already dumped. This allows to avoid blocking
      concurrent DDL on this table without sacrificing correctness, as we
      won't access table second time and dumps created by --single-transaction
      mode have validity point at the start of transaction anyway.
      Note that this doesn't make --single-transaction mode with concurrent
      DDL safe in general case. It just improves situation for people for whom
      it might be working.
    */

下面具體來測試一下:

第一種情況:

會話1發起事務,並查詢sbtest表的值,然後會話2進行添加列操作,該操作被hang住。
這裏寫圖片描述
第二種情況:

會話1發起事務,然後會話2進行添加列操作,發現該操作成功。
這裏寫圖片描述
第三種情況:

模仿mysqldump的備份原理,設置斷點。

注意,DDL操作發起的時間是在執行了select * from test之後,如果是在之前,根據上面第二種情況的測試,是可以進行DDL操作的。

此時,如果不執行ROLLBACK TO SAVEPOINT sp,DDL操作會一直hang下去,執行了該操作後,DDL操作可以繼續執行了。

由此可見,ROLLBACK TO SAVEPOINT確實可以提高DDL的併發性。

但還有一點需要注意,如果DDL操作是發生在select * from test之前,正如第二種情況所演示的,DDL操作會成功,此時,查看test表的數據會報以下錯誤:

root@test 04:32:49 > select * from test;
ERROR 1412 (HY000): Table definition has changed, please retry transaction

對應mysqldump,會報如下錯誤:

mysqldump: Error 1412: Table definition has changed, please retry transaction when dumping table `test` at row: 0

這裏寫圖片描述

總結:

  1. mysqldump的本質是通過select * from tab來獲取表的數據的。

  2. START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT /必須放到FLUSH TABLES WITH READ LOCK和UNLOCK TABLES之間,放到之前會造成START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT /和FLUSH TABLES WITH READ LOCK之間執行的DML語句丟失,放到之後,會造成從庫重複插入數據。

  3. mysqldump只適合放到業務低峯期做,如果備份的過程中數據操作很頻繁,會造成Undo表空間越來越大,undo表空間默認是放到共享表空間中的,而ibdata的特性是一旦增大,就不會收縮。

  4. mysqldump的效率還是比較低下,START TRANSACTION /!40100 WITH CONSISTENT SNAPSHOT /只能等到所有表備份完後才結束,其實效率比較高的做法是備份完一張表就提交一次,這樣可儘快釋放Undo表空間快照佔用的空間。但這樣做,就無法實現對所有表的一致性備份。

  5. 當有大事務正在執行的時候,會等很久。。。

  6. 對於數據庫中非innodb存儲引擎的表,不能保證一致性。主要是指myisam表。加了–single-transaction就能保證innodb的數據是完全一致的,而myisam引擎無法保證,必須加–lock-all-tables。
  7. myisam引擎爲什麼無法保證在–single-transaction下得到一致性的備份?
    因爲它壓根就不支持事務,自然就無法實現上述的過程,雖然添加了–single-transaction參數的myisam表處理過程和上面的完全一致,但是因爲不支持事務,在整個dump過程中無法保證可重複讀,無法得到一致性的備份。而innodb在備份過程中,雖然其他線程也在寫數據,但是dump出來的數據能保證是備份開始時那個binlog pos的數據。
  8. myisam引擎也要保證得到一致性的數據的話,他是如何實現的呢?
    它是通過添加–lock-all-tables,這樣在flush tables with read lock後,直到整個dump過程結束,斷開線程後纔會unlock tables釋放鎖(沒必要主動發unlock tables指令),整個dump過程其他線程不可寫,從而保證數據的一致性。也就是說沒有第六步6.UNLOCK TABLES。

  9. 5.6的mysqldump利用保存點機制,每備份完一個表就將一個表上的MDL鎖釋放,避免對一張表鎖更長的時間。

  10. 大家可能有一個疑問,爲啥備份innodb表之前,就已經將鎖釋放掉了,這實際上是利用了innodb引擎的MVCC機制,開啓快照讀後,就能獲取那個時間的一致的數據,無論需要備份多長時間,直到整個事務結束(commit)爲止。

  11. 爲什麼備份完成後沒有commit操作
    最後並沒有看到commit,因爲在整個事務中,其實並沒有修改任何數據,只是爲了保證可重複讀得到備份時間點一致性的快照,dump完成後提交不提交應該無所謂了。

  /*
    No reason to explicitely COMMIT the transaction, neither to explicitely
    UNLOCK TABLES: these will be automatically be done by the server when we
    disconnect now. Saves some code here, some network trips, adds nothing to
    server.
  */

參考:
1.mysqldump的實現原理(包括部分圖片解釋)
http://www.cnblogs.com/ivictor/p/5505307.html
2.FLUSH TABLE WITH READ LOCK詳解
http://www.cnblogs.com/cchust/p/4603599.html
3.MySQL備份原理詳解
http://www.cnblogs.com/cchust/p/5452557.html

發佈了107 篇原創文章 · 獲贊 22 · 訪問量 20萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章