自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

mysqldump一致性熱備原理剖析

數(shù)據(jù)庫 MySQL
mysqldump是實(shí)際場景中最常使用的備份工具之一,通過選擇合適的選項(xiàng)做備份,mysqldump可以保證數(shù)據(jù)的一致性,同時盡可能保證進(jìn)行中的業(yè)務(wù)不受影響。

 引言

在日常數(shù)據(jù)庫運(yùn)維中,經(jīng)常要對數(shù)據(jù)庫進(jìn)行熱備。熱備的一個關(guān)鍵點(diǎn)是保證數(shù)據(jù)的一致性,即在備份進(jìn)行時發(fā)生的數(shù)據(jù)更改,不會在備份結(jié)果中出現(xiàn)。mysqldump是實(shí)際場景中最常使用的備份工具之一,通過選擇合適的選項(xiàng)做備份,mysqldump可以保證數(shù)據(jù)的一致性,同時盡可能保證進(jìn)行中的業(yè)務(wù)不受影響。

那么mysqldump是如何實(shí)現(xiàn)一致性備份的?以下我將結(jié)合mysqldump過程中mysqld生成的general log與mysqldump的源碼來解釋mysqldump一致性備份的原理。

注:以下的實(shí)例基于MySQL 8.0.18,在不同版本上mysqldump的部分實(shí)現(xiàn)會有不同

首先用mysqldump執(zhí)行一次一致性備份: 

  1. $ mysqldump -uroot -p --skip-opt --default-character-set=utf8  --single-transaction --master-data=2 --no-autocommit -B d1> backup.sql 

關(guān)鍵參數(shù)解釋:

  •  --single-transaction:執(zhí)行一致性備份。
  •  --master-data=2:要求dump結(jié)果中以注釋形式保存?zhèn)浞輹r的binlog位置信息。
  •  -B:指定要dump的數(shù)據(jù)庫,在這里d1是一個使用InnoDB作為存儲引擎的庫,其中只有一個表t1。

執(zhí)行完成后可以得到mysqld生成的general log,里面記錄了mysqldump在備份過程中傳給server的指令。

其中關(guān)鍵的步驟我用框框作了標(biāo)記,具體的解釋請看下文。

mysqldump一致性備份的主要執(zhí)行流程

  1.  連接server
  2.  兩次關(guān)閉所有表,第二次關(guān)表同時加讀鎖
  3.  設(shè)置隔離級別為“可重復(fù)讀”,開始事務(wù)并創(chuàng)建快照
  4.  獲取當(dāng)前binlog位置
  5.  解鎖所有表
  6.  對指定的庫與表進(jìn)行dump

下面結(jié)合SQL內(nèi)容與源碼對以上主要步驟進(jìn)行依次介紹。

流程剖析

1. 連接server

mysqldump首先與server建立連接,并初始化session,set一些session級的變量,對應(yīng)SQL如下圖

其在main函數(shù)中對應(yīng)的源碼就是一個對connect_to_db函數(shù)的調(diào)用: 

  1. if (connect_to_db(current_host, current_user, opt_password)) {  
  2.   free_resources();  
  3.   exit(EX_MYSQLERR); 

2. 兩次關(guān)閉所有表,第二次關(guān)表同時加讀鎖

連接建立后,mysqldump緊接著執(zhí)行兩次關(guān)表操作,并在第二次關(guān)表同時給所有表加上讀鎖,對應(yīng)SQL如下圖:

這一部分在main函數(shù)中對應(yīng)的源碼為: 

  1. if ((opt_lock_all_tables || opt_master_data ||  
  2.      (opt_single_transaction && flush_logs)) &&  
  3.     do_flush_tables_read_lock(mysql))  
  4.   goto err; 

可以看到實(shí)際操作由do_flush_tables_read_lock函數(shù)進(jìn)行,但是這里需要注意操作執(zhí)行的前提條件,觀察代碼我們可以知道,這個關(guān)表操作只會在三種情況下進(jìn)行:

  1.  通過--lock-all-tables選項(xiàng)顯式要求給所有表加鎖。
  2.  通過--master-data選項(xiàng)要求dump出來的結(jié)果中包含binlog位置。
  3.  通過--single-transaction指定了進(jìn)行單事務(wù)的一致性備份,同時通過--flush-logs要求刷新log文件。

看到這里不難知道,除了第一種情況顯式要求加鎖之外,情況3要求刷新log前沒有其他事務(wù)在進(jìn)行寫操作,自然要對所有表加上讀鎖。情況2要求dump結(jié)果中準(zhǔn)確記錄dump進(jìn)行時刻的binlog位置,為了準(zhǔn)確地得到當(dāng)前binlog的位置,自然就需要給所有的表加共享鎖,防止其他并行事務(wù)進(jìn)行寫操作導(dǎo)致binlog更新,因此這里才有一個關(guān)表、加讀鎖的動作。

這里有一個細(xì)節(jié),我們知道--single-transaction選項(xiàng)可以執(zhí)行一致性備份,那么在只有--single-transaction選項(xiàng)時為什么不需要進(jìn)行關(guān)表與加讀鎖的動作呢?這是因?yàn)?-single-transaction所保證的一致性備份依賴于支持事務(wù)的存儲引擎(如InnoDB),在后面會提到,mysqldump通過執(zhí)行START TRANSACTION WITH CONSISTENT SNAPSHOT會創(chuàng)建一個數(shù)據(jù)庫當(dāng)前的快照與一個事務(wù)id,所有在該事務(wù)之后的事務(wù)所進(jìn)行的數(shù)據(jù)更新都會被過濾,以此來保證備份的一致性。這種方式的優(yōu)勢在于不會在進(jìn)行一致性備份時干擾其他事務(wù)的正常進(jìn)行,實(shí)現(xiàn)了所謂的“熱備”,但是缺點(diǎn)在于其依賴事務(wù)型存儲引擎,對于使用MyISAM等不支持事務(wù)的存儲引擎的表,--single-transaction無法保證它們的數(shù)據(jù)一致性。

接著查看do_flush_tables_read_lock函數(shù)的源碼: 

  1. static int do_flush_tables_read_lock(MYSQL *mysql_con) {  
  2.  return (mysql_query_with_error_report(  
  3.              mysql_con, 0,  
  4.             ((opt_master_data != 0) ? "FLUSH /*!40101 LOCAL */ TABLES"  
  5.                                     : "FLUSH TABLES")) ||  
  6.          mysql_query_with_error_report(mysql_con, 0,  
  7.                                        "FLUSH TABLES WITH READ LOCK"));  

可以看到邏輯比較簡單,就是向server傳入執(zhí)行兩個query,依先后次序分別時FLUSH TABLES和FLUSH TABLES WITH READ LOCK,這里核心的動作在于后面一個query,之所以需要前面的FLUSH TABLES是基于性能的考量,以盡可能減少加鎖對其他事務(wù)的影響。

3. 設(shè)置隔離級別為“可重復(fù)讀”,開始事務(wù)并創(chuàng)建快照

關(guān)表操作執(zhí)行完后,mysqldump接著開啟一個新事務(wù)并創(chuàng)建快照,對應(yīng)SQL如下圖:

這一部分在main函數(shù)中對應(yīng)的源碼為: 

  1. if (opt_single_transaction && start_transaction(mysql)) goto err; 

可以看到,只有在指定--single-transaction選項(xiàng)時這一步驟才會執(zhí)行。實(shí)際上這一步就是mysqldump實(shí)現(xiàn)一致性熱備的基礎(chǔ),我們接著查看start_transaction函數(shù)的源碼:

 

  1. static int start_transaction(MYSQL *mysql_con) {  
  2.  // 省略部分非關(guān)鍵代碼與注釋  
  3.  return (  
  4.      mysql_query_with_error_report(mysql_con, 0,  
  5.                                    "SET SESSION TRANSACTION ISOLATION "  
  6.                                    "LEVEL REPEATABLE READ") ||  
  7.      mysql_query_with_error_report(mysql_con, 0,  
  8.                                    "START TRANSACTION "  
  9.                                    "/*!40100 WITH CONSISTENT SNAPSHOT */"));  

可以看到核心動作是傳給server執(zhí)行的兩個query,先是SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ確保當(dāng)前會話的隔離級別是“可重復(fù)讀”,然后通過START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */來開始一個新事務(wù),產(chǎn)生一個新事務(wù)id,同時創(chuàng)建一個快照,dump過程中所使用的數(shù)據(jù)都基于這個快照。這樣,所有在該事務(wù)之后的事務(wù)所進(jìn)行的數(shù)據(jù)更新都會被過濾,備份的數(shù)據(jù)一致性因此得以保證。

但是,這樣的熱備方法,依賴于像InnoDB這樣支持事務(wù)的存儲引擎。相反,如MyISAM這種不支持事務(wù)的存儲引擎在備份過程中的數(shù)據(jù)一致性則不能被保證。

4. 獲取當(dāng)前binlog位置

隨后mysqldump執(zhí)行一個SHOW MASTER STATUS的query,以獲取當(dāng)前binlog的位置信息:

查看main函數(shù)中對應(yīng)部分的源碼可以看到,只有在指定--master-data選項(xiàng)時才會去獲取、記錄當(dāng)前的binlog位置: 

  1. if (opt_master_data && do_show_master_status(mysql)) goto err; 

查看do_show_master_status函數(shù)的實(shí)現(xiàn),可以看到核心動作就是向server傳入執(zhí)行一個SHOW MASTER STATUS的query,最后將得到的binlog位置信息寫入dump結(jié)果中。 

  1. static int do_show_master_status(MYSQL *mysql_con) {  
  2.   MYSQL_ROW row;  
  3.   MYSQL_RES *master;  
  4.   const char *comment_prefix =  
  5.       (opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : "";  
  6.   if (mysql_query_with_error_report(mysql_con, &master, "SHOW MASTER STATUS")) {  
  7.     return 1;  
  8.   } else {  
  9.     row = mysql_fetch_row(master); 
  10.      if (row && row[0] && row[1]) {  
  11.       print_comment(md_result_file, 0,  
  12.                     "\n--\n-- Position to start replication or point-in-time "  
  13.                     "recovery from\n--\n\n");  
  14.       // 寫入dump結(jié)果  
  15.       fprintf(md_result_file,  
  16.               "%sCHANGE MASTER TO MASTER_LOG_FILE='%s'MASTER_LOG_POS=%s;\n",  
  17.               comment_prefix, row[0], row[1]);  
  18.       check_io(md_result_file);  
  19.     }  
  20.     // ...  
  21.   }  
  22.   return 0;  

5. 解鎖所有表

在正式開始dump操作之前,mysqldump會把前面操作中可能加了鎖的表全部解鎖:

查看main函數(shù)中對應(yīng)部分代碼: 

  1. if (opt_single_transaction &&  
  2.     do_unlock_tables(mysql)) /* unlock but no commit! */  
  3.   goto err; 

可以看到,只有在指定了--single-transaction選項(xiàng)時才會解鎖所有先前被加鎖的表,結(jié)合前面的思考可以推斷,--single-transaction下所進(jìn)行的備份通過事務(wù)性質(zhì)可以保證數(shù)據(jù)的一致性,沒有必要再保留對所有表所加的鎖,因此這里執(zhí)行解鎖,以免阻塞其他事務(wù)的進(jìn)行。

6. 對指定的庫與表進(jìn)行dump

前面的準(zhǔn)備操作進(jìn)行完成后,mysqldump開始正式進(jìn)行選定庫、表的dump操作:

對指定數(shù)據(jù)庫的實(shí)際dump由dump_databases函數(shù)執(zhí)行(當(dāng)指定了--all-databases要求dump所有庫時,則由dump_all_databases函數(shù)執(zhí)行)。

查看dump_databases函數(shù)的實(shí)現(xiàn): 

  1. static int dump_databases(char **db_names) {  
  2.   int result = 0 
  3.   char **db;  
  4.   DBUG_TRACE;  
  5.   for (db = db_names; *db; db++) {  
  6.     if (is_infoschema_db(*db))  
  7.       die(EX_USAGE, "Dumping \'%s\' DB content is not supported", *db);  
  8.     if (dump_all_tables_in_db(*db)) result = 1 
  9.   }  
  10.   if (!result && seen_views) {  
  11.     for (db = db_names; *db; db++) {  
  12.       if (dump_all_views_in_db(*db)) result = 1 
  13.     }  
  14.   }  
  15.   return result;  
  16. } /* dump_databases */ 

邏輯比較清晰,先dump每個指定的數(shù)據(jù)庫中所有的表,之后如果存在視圖,則將對應(yīng)視圖也進(jìn)行dump。我們的考察重點(diǎn)放在對表的dump上。

實(shí)際dump一個表的操作邏輯也比較清晰,就是先獲取表的結(jié)構(gòu)信息,得到表的創(chuàng)建語句,然后獲取表中每行的實(shí)際數(shù)據(jù)并生成對應(yīng)的insert語句。

不過,前面的general log中有個值得注意的點(diǎn)是SAVEPOINT的出現(xiàn),這一點(diǎn)在MySQL 5.5的mysqldump中是沒有的,查看dump_all_tables_in_db函數(shù)的實(shí)現(xiàn),可以找到設(shè)置savepoint的對應(yīng)代碼: 

  1. // 創(chuàng)建savepoint  
  2.  if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) {  
  3.    verbose_msg("-- Setting savepoint...\n");  
  4.    if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp")) return 1;  
  5.  }  
  6.  while ((table = getTableName(0))) {  
  7.    char *end = my_stpcpy(afterdot, table);  
  8.    if (include_table(hash_key, end - hash_key)) { 
  9.      dump_table(table, database); // 對表進(jìn)行dump  
  10.      // 省略部分代碼...  
  11.      // ROLLBACK操作 
  12.      /**  
  13.        ROLLBACK TO SAVEPOINT in --single-transaction mode to release metadata  
  14.        lock on table which was already dumped. This allows to avoid blocking  
  15.        concurrent DDL on this table without sacrificing correctness, as we  
  16.        won't access table second time and dumps created by --single-transaction  
  17.        mode have validity point at the start of transaction anyway.  
  18.        Note that this doesn't make --single-transaction mode with concurrent 
  19.         DDL safe in general case. It just improves situation for people for whom  
  20.        it might be working.  
  21.      */  
  22.      if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) {  
  23.        verbose_msg("-- Rolling back to savepoint sp...\n");  
  24.        if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp"))  
  25.          maybe_exit(EX_MYSQLERR);  
  26.      } 

可以看到創(chuàng)建savepoint是在dump表之前,之后遍歷庫中的每個表,每當(dāng)dump完一個表之后,便執(zhí)行一次ROLLBACK TO SAVEPOINT sp操作,為什么呢?其實(shí)上面代碼的注釋已經(jīng)解釋清楚了:

簡單來說,當(dāng)我們dump完一個表后后面都不再需要使用這個表,這時其他事務(wù)的DDL操作不會影響我們dump得到數(shù)據(jù)的正確性,增加savepoint的意義在于,假如我們要dump表A,savepoint記錄了dump表A之前尚未給表A加MDL鎖的狀態(tài),當(dāng)開始dump表A時,由于要進(jìn)行一系列select操作,會給表A加上MDL鎖防止其他事務(wù)的DDL操作改變表結(jié)構(gòu)導(dǎo)致讀動作出錯;最后當(dāng)對表A的dump完成后,后續(xù)都不會再訪問表A了,此時沒有釋放的MDL鎖沒有意義,反而會阻塞其他并行事務(wù)對表A的DDL操作。

對此,MySQL的解決方法是在訪問表A前通過SAVEPOINT sp記錄一個savepoint,在dump完表A之后通過ROLLBACK TO SAVEPOINT sp回到當(dāng)時的狀態(tài),即可釋放對表A加的MDL鎖,放行其他事務(wù)對該表的DDL操作。

小結(jié)

以上是mysqldump基于MySQL 8.0的一致性備份原理介紹,相比MySQL 5.5,現(xiàn)如今MySQL 8.0在mysqldump的實(shí)現(xiàn)存在一定改進(jìn),除了上面提到的savepoint機(jī)制是一個顯著區(qū)別之外,還有諸如對GTID的支持、對column statistics的dump操作在本文中沒有提及,但總體而言,mysqldump在一致性備份上的實(shí)現(xiàn)原理并沒有多少改變。

拓展閱讀——Percona的實(shí)現(xiàn)

MySQL從出現(xiàn)到普及,中途也出現(xiàn)了其他不少優(yōu)秀的發(fā)行版,MySQL中一致性備份的實(shí)現(xiàn)其實(shí)也并不完美,因此如果能夠考量其他發(fā)行版在這方面上的實(shí)現(xiàn),也是一件有意義的事情。

Backup Lock

在前面我有提到,mysqldump中--single-transaction選項(xiàng)所實(shí)現(xiàn)的一致性備份不需要對表加鎖,但這一特性基于事務(wù)型的存儲引擎,因此只對InnoDB表或使用其他事務(wù)型存儲引擎類型的表能夠保證備份時過濾掉其他并行事務(wù)的更新操作;但對使用了MyISAM這種不支持事務(wù)的存儲引擎的表,--single-transaction無法保證其數(shù)據(jù)的一致性,即若備份過程中出現(xiàn)了來自其他并行事務(wù)的更新操作,其很有可能被寫入了備份中。

既然如此,若想對MyISAM的表進(jìn)行備份,又想保證其一致性該怎么辦?一種方式可以是在執(zhí)行mysqldump時傳入--lock-all-tables選項(xiàng),這個選項(xiàng)會使得dump操作進(jìn)行之前執(zhí)行一個FLUSH TABLES WITH READ LOCK語句,并保證在dump的全程保持對所有表的讀鎖。但是無疑這是一種overkill,僅僅是為了保證一部分非事務(wù)型存儲引擎的表的一致性,就需要對所有表加鎖,進(jìn)而業(yè)務(wù)上所有對server的寫操作被阻塞一段時間(若備份的數(shù)據(jù)量大,這簡直會造成一場災(zāi)難)。

這一問題,我尚未在MySQL 8.0中找到相應(yīng)的好的解決方式,不過Percona對此給出了一個方案:在Percona發(fā)行版的mysqldump中,執(zhí)行時可以傳入一個--lock-for-backup選項(xiàng),這個選項(xiàng)會使得mysqldump在dump之前,執(zhí)行一個LOCK TABLES FOR BACKUP語句,這是一個Percona獨(dú)有的query,其主要做以下幾件事情:

  •  阻塞對MyISAM, MEMORY, CSV, ARCHIVE表的更新操作;
  •  阻塞對任何表的DDL操作;
  •  不阻塞對臨時表與log表的更新操作。

顯然,有了以上的特性,當(dāng)同時傳入--lock-for-backup與--single-transaction兩個選項(xiàng)同時,mysqldump可以保證所有表的數(shù)據(jù)一致性,并且盡可能保證造成最少的線上業(yè)務(wù)干擾。

這一部分邏輯可以在Percona Server 8.0中mysqldump的代碼中找到,在main函數(shù)中: 

  1. if (opt_lock_all_tables ||  
  2.     (opt_master_data &&  
  3.      (!has_consistent_binlog_pos || !has_consistent_gtid_executed)) ||  
  4.     (opt_single_transaction && flush_logs)) {  
  5.   if (do_flush_tables_read_lock(mysql)) goto err;  
  6.   ftwrl_done = true 
  7. } else if (opt_lock_for_backup && do_lock_tables_for_backup(mysql))  
  8.   goto err; 

細(xì)心的朋友會發(fā)現(xiàn),這是對上面的“關(guān)表加讀鎖操作”進(jìn)行的邏輯改寫,其增加了一個else if邏輯分支,取代了之前的FLUSH TABLES; FLUSH TABLES WITH READ LOCK;操作,主要目的是為了與--single-transaction進(jìn)行的一致性備份更好地兼容,實(shí)現(xiàn)對線上業(yè)務(wù)盡可能少的阻塞。

接著查看do_lock_tables_for_backup函數(shù)的實(shí)現(xiàn),可以看到就是簡單地向server傳入一個Percona獨(dú)有的LOCK TABLES FOR BACKUP語句: 

  1. static int do_lock_tables_for_backup(MYSQL *mysql_con) noexcept {  
  2.   return mysql_query_with_error_report(mysql_con, 0, "LOCK TABLES FOR BACKUP");  

Binlog Snapshot

在MySQL 8.0的實(shí)現(xiàn)中,有一個常用的選項(xiàng),仍然會導(dǎo)致“討人厭”的FLUSH TABLES WITH READ LOCK的執(zhí)行,即--master-data選項(xiàng)。

前面提到,--master-data選項(xiàng)要求在dump之后的結(jié)果中存有當(dāng)前備份開始時的binlog位置,為了滿足所獲得binlog位置的一致性,需要在執(zhí)行SHOW MASTER STATUS前,獲取對所有表的讀鎖以阻塞所有binlog的提交事件,因此要求執(zhí)行一次FLUSH TABLES WITH READ LOCK。但是有沒有更好的方式?Percona同樣給出了自己的解決方法。

在Percona Server中,新增了兩個全局status:Binlog_snapshot_file和Binlog_snapshot_pos,分別用來記錄當(dāng)前的binlog文件與binlog位置,通過SHOW STATUS LIKE 'binlog_snapshot_%'即可獲取兩個status的值。那么使用這個方式,跟SHOW MASTER STATUS有什么區(qū)別?

二者的區(qū)別在于,Binlog_snapshot_file和Binlog_snapshot_pos這兩個status具有事務(wù)性,只要在執(zhí)行SHOW STATUS LIKE 'binlog_snapshot_%'這個語句之前通過START TRANSACTION WITH CONSISTENT SNAPSHOT創(chuàng)建了新事務(wù)與一致性快照,Binlog_snapshot_file和Binlog_snapshot_pos所記錄的則正是該事務(wù)開始時的binlog文件與位置信息,進(jìn)而binlog信息的一致性得到保證,而這一過程的全程都不需要FLUSH TABLES WITH READ LOCK的執(zhí)行。

相對的,SHOW MASTER STATUS是不具備事務(wù)性的,每次執(zhí)行該語句返回的都是當(dāng)前最新的binlog位置信息,這也是為什么執(zhí)行它之前需要對所有表上讀鎖。 

 

責(zé)任編輯:龐桂玉 來源: 老葉茶館
相關(guān)推薦

2018-08-08 15:51:44

Hash分布式算法

2020-11-24 09:03:41

一致性MySQLMVCC

2022-03-22 09:54:22

Hash算法

2017-06-27 09:40:28

MYSQL數(shù)據(jù)備份

2017-07-25 14:38:56

數(shù)據(jù)庫一致性非鎖定讀一致性鎖定讀

2024-04-10 10:34:34

Cache系統(tǒng)GPU

2022-12-14 08:23:30

2021-02-05 08:00:48

哈希算法?機(jī)器

2021-02-02 12:40:50

哈希算法數(shù)據(jù)

2021-11-12 08:38:26

一致性哈希算法數(shù)據(jù)結(jié)構(gòu)

2023-08-14 08:10:33

CPU緩存RFO

2020-05-12 10:43:22

Redis緩存數(shù)據(jù)庫

2022-10-19 12:22:53

并發(fā)扣款一致性

2021-06-30 21:13:49

CPUCache數(shù)據(jù)

2021-02-04 06:30:26

Python編程語言

2022-11-10 07:49:09

hash算法代碼

2017-07-02 16:28:06

MySQL數(shù)據(jù)庫集群

2017-05-19 15:00:05

session架構(gòu)web-server

2020-03-16 11:55:28

PaxosRaft協(xié)議

2021-07-26 06:33:42

CRDT數(shù)據(jù)CAP
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號