您好,登錄后才能下訂單哦!
mysqldump一致性熱備的原理是什么,相信很多沒有經(jīng)驗(yàn)的人對(duì)此束手無策,為此本文總結(jié)了問題出現(xiàn)的原因和解決方法,通過這篇文章希望你能解決這個(gè)問題。
首先用mysqldump執(zhí)行一次一致性備份:
$ 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(shí)的binlog位置信息。
-B:指定要dump的數(shù)據(jù)庫(kù),在這里d1是一個(gè)使用InnoDB作為存儲(chǔ)引擎的庫(kù),其中只有一個(gè)表t1。
執(zhí)行完成后可以得到mysqld生成的general log,里面記錄了mysqldump在備份過程中傳給server的指令。
其中關(guān)鍵的步驟我用框框作了標(biāo)記,具體的解釋請(qǐng)看下文。
mysqldump一致性備份的主要執(zhí)行流程
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
連接server
兩次關(guān)閉所有表,第二次關(guān)表同時(shí)加讀鎖
設(shè)置隔離級(jí)別為“可重復(fù)讀”,開始事務(wù)并創(chuàng)建快照
獲取當(dāng)前binlog位置
解鎖所有表
對(duì)指定的庫(kù)與表進(jìn)行dump
下面結(jié)合SQL內(nèi)容與源碼對(duì)以上主要步驟進(jìn)行依次介紹。
流程剖析
1. 連接server
mysqldump首先與server建立連接,并初始化session,set一些session級(jí)的變量,對(duì)應(yīng)SQL如下圖
其在main函數(shù)中對(duì)應(yīng)的源碼就是一個(gè)對(duì)connect_to_db函數(shù)的調(diào)用:
if (connect_to_db(current_host, current_user, opt_password)) { free_resources(); exit(EX_MYSQLERR);
2. 兩次關(guān)閉所有表,第二次關(guān)表同時(shí)加讀鎖
連接建立后,mysqldump緊接著執(zhí)行兩次關(guān)表操作,并在第二次關(guān)表同時(shí)給所有表加上讀鎖,對(duì)應(yīng)SQL如下圖:
這一部分在main函數(shù)中對(duì)應(yīng)的源碼為:
if ((opt_lock_all_tables || opt_master_data || (opt_single_transaction && flush_logs)) && do_flush_tables_read_lock(mysql)) goto err;
可以看到實(shí)際操作由do_flush_tables_read_lock函數(shù)進(jìn)行,但是這里需要注意操作執(zhí)行的前提條件,觀察代碼我們可以知道,這個(gè)關(guān)表操作只會(huì)在三種情況下進(jìn)行:
鴻蒙官方戰(zhàn)略合作共建——HarmonyOS技術(shù)社區(qū)
通過--lock-all-tables選項(xiàng)顯式要求給所有表加鎖。
通過--master-data選項(xiàng)要求dump出來的結(jié)果中包含binlog位置。
通過--single-transaction指定了進(jìn)行單事務(wù)的一致性備份,同時(shí)通過--flush-logs要求刷新log文件。
看到這里不難知道,除了第一種情況顯式要求加鎖之外,情況3要求刷新log前沒有其他事務(wù)在進(jìn)行寫操作,自然要對(duì)所有表加上讀鎖。情況2要求dump結(jié)果中準(zhǔn)確記錄dump進(jìn)行時(shí)刻的binlog位置,為了準(zhǔn)確地得到當(dāng)前binlog的位置,自然就需要給所有的表加共享鎖,防止其他并行事務(wù)進(jìn)行寫操作導(dǎo)致binlog更新,因此這里才有一個(gè)關(guān)表、加讀鎖的動(dòng)作。
這里有一個(gè)細(xì)節(jié),我們知道--single-transaction選項(xiàng)可以執(zhí)行一致性備份,那么在只有--single-transaction選項(xiàng)時(shí)為什么不需要進(jìn)行關(guān)表與加讀鎖的動(dòng)作呢?這是因?yàn)?-single-transaction所保證的一致性備份依賴于支持事務(wù)的存儲(chǔ)引擎(如InnoDB),在后面會(huì)提到,mysqldump通過執(zhí)行START TRANSACTION WITH CONSISTENT SNAPSHOT會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)當(dāng)前的快照與一個(gè)事務(wù)id,所有在該事務(wù)之后的事務(wù)所進(jìn)行的數(shù)據(jù)更新都會(huì)被過濾,以此來保證備份的一致性。這種方式的優(yōu)勢(shì)在于不會(huì)在進(jìn)行一致性備份時(shí)干擾其他事務(wù)的正常進(jìn)行,實(shí)現(xiàn)了所謂的“熱備”,但是缺點(diǎn)在于其依賴事務(wù)型存儲(chǔ)引擎,對(duì)于使用MyISAM等不支持事務(wù)的存儲(chǔ)引擎的表,--single-transaction無法保證它們的數(shù)據(jù)一致性。
接著查看do_flush_tables_read_lock函數(shù)的源碼:
static int do_flush_tables_read_lock(MYSQL *mysql_con) { return (mysql_query_with_error_report( mysql_con, 0, ((opt_master_data != 0) ? "FLUSH /*!40101 LOCAL */ TABLES" : "FLUSH TABLES")) || mysql_query_with_error_report(mysql_con, 0, "FLUSH TABLES WITH READ LOCK")); }
可以看到邏輯比較簡(jiǎn)單,就是向server傳入執(zhí)行兩個(gè)query,依先后次序分別時(shí)FLUSH TABLES和FLUSH TABLES WITH READ LOCK,這里核心的動(dòng)作在于后面一個(gè)query,之所以需要前面的FLUSH TABLES是基于性能的考量,以盡可能減少加鎖對(duì)其他事務(wù)的影響。
3. 設(shè)置隔離級(jí)別為“可重復(fù)讀”,開始事務(wù)并創(chuàng)建快照
關(guān)表操作執(zhí)行完后,mysqldump接著開啟一個(gè)新事務(wù)并創(chuàng)建快照,對(duì)應(yīng)SQL如下圖:
這一部分在main函數(shù)中對(duì)應(yīng)的源碼為:
if (opt_single_transaction && start_transaction(mysql)) goto err;
可以看到,只有在指定--single-transaction選項(xiàng)時(shí)這一步驟才會(huì)執(zhí)行。實(shí)際上這一步就是mysqldump實(shí)現(xiàn)一致性熱備的基礎(chǔ),我們接著查看start_transaction函數(shù)的源碼:
static int start_transaction(MYSQL *mysql_con) { // 省略部分非關(guān)鍵代碼與注釋 return ( mysql_query_with_error_report(mysql_con, 0, "SET SESSION TRANSACTION ISOLATION " "LEVEL REPEATABLE READ") || mysql_query_with_error_report(mysql_con, 0, "START TRANSACTION " "/*!40100 WITH CONSISTENT SNAPSHOT */")); }
可以看到核心動(dòng)作是傳給server執(zhí)行的兩個(gè)query,先是SET SESSION TRANSACTION ISOLATION LEVEL REPEATABLE READ確保當(dāng)前會(huì)話的隔離級(jí)別是“可重復(fù)讀”,然后通過START TRANSACTION /*!40100 WITH CONSISTENT SNAPSHOT */來開始一個(gè)新事務(wù),產(chǎn)生一個(gè)新事務(wù)id,同時(shí)創(chuàng)建一個(gè)快照,dump過程中所使用的數(shù)據(jù)都基于這個(gè)快照。這樣,所有在該事務(wù)之后的事務(wù)所進(jìn)行的數(shù)據(jù)更新都會(huì)被過濾,備份的數(shù)據(jù)一致性因此得以保證。
但是,這樣的熱備方法,依賴于像InnoDB這樣支持事務(wù)的存儲(chǔ)引擎。相反,如MyISAM這種不支持事務(wù)的存儲(chǔ)引擎在備份過程中的數(shù)據(jù)一致性則不能被保證。
4. 獲取當(dāng)前binlog位置
隨后mysqldump執(zhí)行一個(gè)SHOW MASTER STATUS的query,以獲取當(dāng)前binlog的位置信息:
查看main函數(shù)中對(duì)應(yīng)部分的源碼可以看到,只有在指定--master-data選項(xiàng)時(shí)才會(huì)去獲取、記錄當(dāng)前的binlog位置:
if (opt_master_data && do_show_master_status(mysql)) goto err;
查看do_show_master_status函數(shù)的實(shí)現(xiàn),可以看到核心動(dòng)作就是向server傳入執(zhí)行一個(gè)SHOW MASTER STATUS的query,最后將得到的binlog位置信息寫入dump結(jié)果中。
static int do_show_master_status(MYSQL *mysql_con) { MYSQL_ROW row; MYSQL_RES *master; const char *comment_prefix = (opt_master_data == MYSQL_OPT_MASTER_DATA_COMMENTED_SQL) ? "-- " : ""; if (mysql_query_with_error_report(mysql_con, &master, "SHOW MASTER STATUS")) { return 1; } else { row = mysql_fetch_row(master); if (row && row[0] && row[1]) { print_comment(md_result_file, 0, "\n--\n-- Position to start replication or point-in-time " "recovery from\n--\n\n"); // 寫入dump結(jié)果 fprintf(md_result_file, "%sCHANGE MASTER TO MASTER_LOG_FILE='%s', MASTER_LOG_POS=%s;\n", comment_prefix, row[0], row[1]); check_io(md_result_file); } // ... } return 0; }
5. 解鎖所有表
在正式開始dump操作之前,mysqldump會(huì)把前面操作中可能加了鎖的表全部解鎖:
查看main函數(shù)中對(duì)應(yīng)部分代碼:
if (opt_single_transaction && do_unlock_tables(mysql)) /* unlock but no commit! */ goto err;
可以看到,只有在指定了--single-transaction選項(xiàng)時(shí)才會(huì)解鎖所有先前被加鎖的表,結(jié)合前面的思考可以推斷,--single-transaction下所進(jìn)行的備份通過事務(wù)性質(zhì)可以保證數(shù)據(jù)的一致性,沒有必要再保留對(duì)所有表所加的鎖,因此這里執(zhí)行解鎖,以免阻塞其他事務(wù)的進(jìn)行。
6. 對(duì)指定的庫(kù)與表進(jìn)行dump
前面的準(zhǔn)備操作進(jìn)行完成后,mysqldump開始正式進(jìn)行選定庫(kù)、表的dump操作:
對(duì)指定數(shù)據(jù)庫(kù)的實(shí)際dump由dump_databases函數(shù)執(zhí)行(當(dāng)指定了--all-databases要求dump所有庫(kù)時(shí),則由dump_all_databases函數(shù)執(zhí)行)。
查看dump_databases函數(shù)的實(shí)現(xiàn):
static int dump_databases(char **db_names) { int result = 0; char **db; DBUG_TRACE; for (db = db_names; *db; db++) { if (is_infoschema_db(*db)) die(EX_USAGE, "Dumping \'%s\' DB content is not supported", *db); if (dump_all_tables_in_db(*db)) result = 1; } if (!result && seen_views) { for (db = db_names; *db; db++) { if (dump_all_views_in_db(*db)) result = 1; } } return result; } /* dump_databases */
邏輯比較清晰,先dump每個(gè)指定的數(shù)據(jù)庫(kù)中所有的表,之后如果存在視圖,則將對(duì)應(yīng)視圖也進(jìn)行dump。我們的考察重點(diǎn)放在對(duì)表的dump上。
實(shí)際dump一個(gè)表的操作邏輯也比較清晰,就是先獲取表的結(jié)構(gòu)信息,得到表的創(chuàng)建語句,然后獲取表中每行的實(shí)際數(shù)據(jù)并生成對(duì)應(yīng)的insert語句。
不過,前面的general log中有個(gè)值得注意的點(diǎn)是SAVEPOINT的出現(xiàn),這一點(diǎn)在MySQL 5.5的mysqldump中是沒有的,查看dump_all_tables_in_db函數(shù)的實(shí)現(xiàn),可以找到設(shè)置savepoint的對(duì)應(yīng)代碼:
// 創(chuàng)建savepoint if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) { verbose_msg("-- Setting savepoint...\n"); if (mysql_query_with_error_report(mysql, 0, "SAVEPOINT sp")) return 1; } while ((table = getTableName(0))) { char *end = my_stpcpy(afterdot, table); if (include_table(hash_key, end - hash_key)) { dump_table(table, database); // 對(duì)表進(jìn)行dump // 省略部分代碼... // ROLLBACK操作 /** 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. */ if (opt_single_transaction && mysql_get_server_version(mysql) >= 50500) { verbose_msg("-- Rolling back to savepoint sp...\n"); if (mysql_query_with_error_report(mysql, 0, "ROLLBACK TO SAVEPOINT sp")) maybe_exit(EX_MYSQLERR); }
可以看到創(chuàng)建savepoint是在dump表之前,之后遍歷庫(kù)中的每個(gè)表,每當(dāng)dump完一個(gè)表之后,便執(zhí)行一次ROLLBACK TO SAVEPOINT sp操作,為什么呢?其實(shí)上面代碼的注釋已經(jīng)解釋清楚了:
簡(jiǎn)單來說,當(dāng)我們dump完一個(gè)表后后面都不再需要使用這個(gè)表,這時(shí)其他事務(wù)的DDL操作不會(huì)影響我們dump得到數(shù)據(jù)的正確性,增加savepoint的意義在于,假如我們要dump表A,savepoint記錄了dump表A之前尚未給表A加MDL鎖的狀態(tài),當(dāng)開始dump表A時(shí),由于要進(jìn)行一系列select操作,會(huì)給表A加上MDL鎖防止其他事務(wù)的DDL操作改變表結(jié)構(gòu)導(dǎo)致讀動(dòng)作出錯(cuò);最后當(dāng)對(duì)表A的dump完成后,后續(xù)都不會(huì)再訪問表A了,此時(shí)沒有釋放的MDL鎖沒有意義,反而會(huì)阻塞其他并行事務(wù)對(duì)表A的DDL操作。
對(duì)此,MySQL的解決方法是在訪問表A前通過SAVEPOINT sp記錄一個(gè)savepoint,在dump完表A之后通過ROLLBACK TO SAVEPOINT sp回到當(dāng)時(shí)的狀態(tài),即可釋放對(duì)表A加的MDL鎖,放行其他事務(wù)對(duì)該表的DDL操作。
小結(jié)
以上是mysqldump基于MySQL 8.0的一致性備份原理介紹,相比MySQL 5.5,現(xiàn)如今MySQL 8.0在mysqldump的實(shí)現(xiàn)存在一定改進(jìn),除了上面提到的savepoint機(jī)制是一個(gè)顯著區(qū)別之外,還有諸如對(duì)GTID的支持、對(duì)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)的一致性備份不需要對(duì)表加鎖,但這一特性基于事務(wù)型的存儲(chǔ)引擎,因此只對(duì)InnoDB表或使用其他事務(wù)型存儲(chǔ)引擎類型的表能夠保證備份時(shí)過濾掉其他并行事務(wù)的更新操作;但對(duì)使用了MyISAM這種不支持事務(wù)的存儲(chǔ)引擎的表,--single-transaction無法保證其數(shù)據(jù)的一致性,即若備份過程中出現(xiàn)了來自其他并行事務(wù)的更新操作,其很有可能被寫入了備份中。
既然如此,若想對(duì)MyISAM的表進(jìn)行備份,又想保證其一致性該怎么辦?一種方式可以是在執(zhí)行mysqldump時(shí)傳入--lock-all-tables選項(xiàng),這個(gè)選項(xiàng)會(huì)使得dump操作進(jìn)行之前執(zhí)行一個(gè)FLUSH TABLES WITH READ LOCK語句,并保證在dump的全程保持對(duì)所有表的讀鎖。但是無疑這是一種overkill,僅僅是為了保證一部分非事務(wù)型存儲(chǔ)引擎的表的一致性,就需要對(duì)所有表加鎖,進(jìn)而業(yè)務(wù)上所有對(duì)server的寫操作被阻塞一段時(shí)間(若備份的數(shù)據(jù)量大,這簡(jiǎn)直會(huì)造成一場(chǎng)災(zāi)難)。
這一問題,我尚未在MySQL 8.0中找到相應(yīng)的好的解決方式,不過Percona對(duì)此給出了一個(gè)方案:在Percona發(fā)行版的mysqldump中,執(zhí)行時(shí)可以傳入一個(gè)--lock-for-backup選項(xiàng),這個(gè)選項(xiàng)會(huì)使得mysqldump在dump之前,執(zhí)行一個(gè)LOCK TABLES FOR BACKUP語句,這是一個(gè)Percona獨(dú)有的query,其主要做以下幾件事情:
阻塞對(duì)MyISAM, MEMORY, CSV, ARCHIVE表的更新操作;
阻塞對(duì)任何表的DDL操作;
不阻塞對(duì)臨時(shí)表與log表的更新操作。
顯然,有了以上的特性,當(dāng)同時(shí)傳入--lock-for-backup與--single-transaction兩個(gè)選項(xiàng)同時(shí),mysqldump可以保證所有表的數(shù)據(jù)一致性,并且盡可能保證造成最少的線上業(yè)務(wù)干擾。
這一部分邏輯可以在Percona Server 8.0中mysqldump的代碼中找到,在main函數(shù)中:
if (opt_lock_all_tables || (opt_master_data && (!has_consistent_binlog_pos || !has_consistent_gtid_executed)) || (opt_single_transaction && flush_logs)) { if (do_flush_tables_read_lock(mysql)) goto err; ftwrl_done = true; } else if (opt_lock_for_backup && do_lock_tables_for_backup(mysql)) goto err;
細(xì)心的朋友會(huì)發(fā)現(xiàn),這是對(duì)上面的“關(guān)表加讀鎖操作”進(jìn)行的邏輯改寫,其增加了一個(gè)else if邏輯分支,取代了之前的FLUSH TABLES; FLUSH TABLES WITH READ LOCK;操作,主要目的是為了與--single-transaction進(jìn)行的一致性備份更好地兼容,實(shí)現(xiàn)對(duì)線上業(yè)務(wù)盡可能少的阻塞。
接著查看do_lock_tables_for_backup函數(shù)的實(shí)現(xiàn),可以看到就是簡(jiǎn)單地向server傳入一個(gè)Percona獨(dú)有的LOCK TABLES FOR BACKUP語句:
static int do_lock_tables_for_backup(MYSQL *mysql_con) noexcept { return mysql_query_with_error_report(mysql_con, 0, "LOCK TABLES FOR BACKUP"); }
Binlog Snapshot
在MySQL 8.0的實(shí)現(xiàn)中,有一個(gè)常用的選項(xiàng),仍然會(huì)導(dǎo)致“討人厭”的FLUSH TABLES WITH READ LOCK的執(zhí)行,即--master-data選項(xiàng)。
前面提到,--master-data選項(xiàng)要求在dump之后的結(jié)果中存有當(dāng)前備份開始時(shí)的binlog位置,為了滿足所獲得binlog位置的一致性,需要在執(zhí)行SHOW MASTER STATUS前,獲取對(duì)所有表的讀鎖以阻塞所有binlog的提交事件,因此要求執(zhí)行一次FLUSH TABLES WITH READ LOCK。但是有沒有更好的方式?Percona同樣給出了自己的解決方法。
在Percona Server中,新增了兩個(gè)全局status:Binlog_snapshot_file和Binlog_snapshot_pos,分別用來記錄當(dāng)前的binlog文件與binlog位置,通過SHOW STATUS LIKE 'binlog_snapshot_%'即可獲取兩個(gè)status的值。那么使用這個(gè)方式,跟SHOW MASTER STATUS有什么區(qū)別?
二者的區(qū)別在于,Binlog_snapshot_file和Binlog_snapshot_pos這兩個(gè)status具有事務(wù)性,只要在執(zhí)行SHOW STATUS LIKE 'binlog_snapshot_%'這個(gè)語句之前通過START TRANSACTION WITH CONSISTENT SNAPSHOT創(chuàng)建了新事務(wù)與一致性快照,Binlog_snapshot_file和Binlog_snapshot_pos所記錄的則正是該事務(wù)開始時(shí)的binlog文件與位置信息,進(jìn)而binlog信息的一致性得到保證,而這一過程的全程都不需要FLUSH TABLES WITH READ LOCK的執(zhí)行。
相對(duì)的,SHOW MASTER STATUS是不具備事務(wù)性的,每次執(zhí)行該語句返回的都是當(dāng)前最新的binlog位置信息,這也是為什么執(zhí)行它之前需要對(duì)所有表上讀鎖。
看完上述內(nèi)容,你們掌握mysqldump一致性熱備的原理是什么的方法了嗎?如果還想學(xué)到更多技能或想了解更多相關(guān)內(nèi)容,歡迎關(guān)注億速云行業(yè)資訊頻道,感謝各位的閱讀!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。