mydumper是一個多線程、高性能的數(shù)據(jù)邏輯備份、恢復(fù)的工具,相比MySQL自帶的mysqldump提速不少。我下載了0.23的穩(wěn)定版本,閱讀了源碼并總結(jié)了一些使用的心得。
mysqldump是個單線程的邏輯備份工具,依次一個個導(dǎo)出多個表,沒有一個并行的機(jī)制。mydumper彌補(bǔ)了這方面的缺陷可以并行的多線程的從表中讀入數(shù)據(jù)并同時(shí)寫到不同的文件里。項(xiàng)目的作者是由一群在sun、fb、skysql的工程師完成的。類似的工具還有mk-parallel-dump。
編譯安裝cmake .
make
sudo make install
源碼分析
根據(jù)流程圖解釋一下mydumper的工作步驟。

解析參數(shù)
使用glib的g_option_context_parse,比libc里的getopt_long簡單多了。
連接目標(biāo)數(shù)據(jù)庫。
通過show processlist來判斷是否有長查詢,如果有長查詢則退出dump,可以通過–long-query-guard加長時(shí)間,或者使用–kill-long-queries殺掉長查詢。
鎖定myisam表。
針對innodb table開啟事務(wù)。
產(chǎn)生3個消息隊(duì)列(線程ready隊(duì)列、任務(wù)隊(duì)列、myisam表處理完畢隊(duì)列)。
conf.queue = g_async_queue_new();
conf.ready = g_async_queue_new();
conf.unlock_tables= g_async_queue_new();
產(chǎn)生指定的線程個數(shù),–threads可以指定,默認(rèn)是4個。
GThread **threads = g_new(GThread*,num_threads);
struct thread_data *td= g_new(struct thread_data, num_threads);
for (n=0; n<num_threads; n++) {
td[n].conf= &conf;
td[n].thread_id= n+1;
threads[n] = g_thread_create((GThreadFunc)process_queue,&td[n],TRUE,NULL);
g_async_queue_pop(conf.ready);
}
dump_database,從DATA_DICTIONARY.TABLES讀取所有表,通過–ignore, –tables-list, regex等過濾條件,產(chǎn)生需要dump的目標(biāo)表列表,分別插入innodb_tables、non_innodb_table、 table_schemas三個鏈表。
query= g_strdup_printf("SELECT TABLE_NAME, ENGINE, TABLE_TYPE as COMMENT FROM DATA_DICTIONARY.TABLES WHERE TABLE_SCHEMA='%s'", database);
....
innodb_tables= g_list_append(innodb_tables, dbt);
....
non_innodb_table= g_list_append(non_innodb_table, dbt);
....
table_schemas= g_list_append(table_schemas, dbt);
dump non-innodb table 把需要導(dǎo)出myisam表加入到任務(wù)隊(duì)列。
for (non_innodb_table= g_list_first(non_innodb_table); non_innodb_table; non_innodb_table= g_list_next(non_innodb_table)) {
dbt= (struct db_table*) non_innodb_table->data;
dump_table(conn, dbt->database, dbt->table, &conf, FALSE);
g_atomic_int_inc(&non_innodb_table_counter);
}
dump innodb table把需要導(dǎo)出innodb表加入任務(wù)隊(duì)列。
for (innodb_tables= g_list_first(innodb_tables); innodb_tables; innodb_tables= g_list_next(innodb_tables)) {
dbt= (struct db_table*) innodb_tables->data;
dump_table(conn, dbt->database, dbt->table, &conf, TRUE);
}
dump schema 把需要導(dǎo)出表結(jié)構(gòu)任務(wù)加入到任務(wù)隊(duì)列。
for (table_schemas= g_list_first(table_schemas); table_schemas; table_schemas= g_list_next(table_schemas)) {
dbt= (struct db_table*) table_schemas->data;
dump_schema(dbt->database, dbt->table, &conf);
g_free(dbt->table);
g_free(dbt->database);
g_free(dbt);
}
典型的生產(chǎn)者(主線程)消費(fèi)者(子線程)模式,子線程會從任務(wù)隊(duì)列里讀取需要處理的表名字和表類型,再通過select * from table_name 讀入數(shù)據(jù)各自寫入到各自的文件。
for(;;) {
....
job=(struct job *)g_async_queue_pop(conf->queue);
....
switch (job->type) {
case JOB_DUMP:
....
dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename);
....
case JOB_DUMP_NON_INNODB:
....
dump_table_data_file(thrconn, tj->database, tj->table, tj->where, tj->filename);
case JOB_SCHEMA:
....
dump_schema_data(thrconn, sj->database, sj->table, sj->filename);
}
所有導(dǎo)數(shù)據(jù)的任務(wù)加入任務(wù)隊(duì)列之后,會再加入讓線程退出的任務(wù),讓線程自然退出。
case JOB_SHUTDOWN:
g_message("Thread %d shutting down", td->thread_id);
if (thrconn)
mysql_close(thrconn);
g_free(job);
mysql_thread_end();
return NULL;
break;
解除myisam表鎖。
等待子線程退出。
使用
導(dǎo)出test database的數(shù)據(jù)
mydumper -h 127.0.0.1 -u root --database test
指定某個目錄
mydumper -h 127.0.0.1 -u root --outputdir=.
不導(dǎo)出表結(jié)構(gòu)
mydumper -h 127.0.0.1 -u root --no-schema
如果表數(shù)據(jù)是空,還是產(chǎn)生一個空文件(默認(rèn)無數(shù)據(jù)則只有表結(jié)構(gòu)文件)
mydumper -h 127.0.0.1 -u root --build-empty-files
設(shè)置長查詢的上限,如果存在比這個還長的查詢則退出mydumper,也可以設(shè)置殺掉這個長查詢
mydumper -h 127.0.0.1 -u root --long-query-guard 200 --kill-long-queries
設(shè)置要dump的列表–tables-list,不需要設(shè)置db名字,逗號分割
mydumper -h 127.0.0.1 -u root --tables-list=ddd,zzz
通過regex也設(shè)置正則表達(dá),需要設(shè)置db名字
mydumper -h 127.0.0.1 -u root --regex=test.z
把單表分成多個chunks,這個后面會講分割的原理
mydumper -h 127.0.0.1 -u root --rows 10000
過濾某個引擎的表
mydumper -h 127.0.0.1 -u root -B test --ignore-engines=innodb
詳細(xì)日志
mydumper -h 127.0.0.1 -u root -B test -v 3
幾個注意點(diǎn)
各自線程都要自己連接到數(shù)據(jù)庫,因?yàn)閘ibmysql是線程不安全的。
因?yàn)閷yisam表有有表鎖,所有先處理myisam表,記錄myisam表個數(shù),每處理一個myisam都原子操作數(shù)量減一。并在myisam表都處理完畢后,立即解鎖,盡量減少鎖定的時(shí)間,而不是在導(dǎo)出innodb表數(shù)據(jù)的時(shí)候還在lock myisam表。
main_thread
for (non_innodb_table= g_list_first(non_innodb_table); non_innodb_table; non_innodb_table= g_list_next(non_innodb_table)) {
dbt= (struct db_table*) non_innodb_table->data;
dump_table(conn, dbt->database, dbt->table, &conf, FALSE);
g_atomic_int_inc(&non_innodb_table_counter);
}
child_thread
if (g_atomic_int_dec_and_test(&non_innodb_table_counter) && g_atomic_int_get(&non_innodb_done)) {
g_async_queue_push(conf->unlock_tables, GINT_TO_POINTER(1));
}
main_thread
g_async_queue_pop(conf.unlock_tables);
g_message("Non-InnoDB dump complete, unlocking tables");
mysql_query(conn, "UNLOCK TABLES");
–regex的處理在–tables-list后, 先滿足–tables-list再滿足–regex,如下只會dump表z1
mydumper -h 127.0.0.1 -u root --regex=test.z1 --outputdir=. --rows=10000 -v 3 -e --tables-list=z2,z1
** Message: Thread 1 dumping data for `test`.`z1`
** Message: Thread 2 dumping schema for `test`.`z1`
–rows的使用,設(shè)置–rows可以把一個表分成多個文件。分塊的原則并不是根據(jù)–rows設(shè)定的行數(shù)來決定生成文件里包含的函數(shù),而是通過rows和表的總行數(shù)計(jì)算出要生成的文件個數(shù),盡量保證每個文件的大小一致。
表的總行數(shù)是如何獲得的?
首先mydumper會選擇一個索引,順序是pk、uk或者show index from table里Cardinality最高的一個索引,再通過explain select index from table的rows字段獲得總行數(shù)total_nums(可能不準(zhǔn)確),于是第一個文件就是從select * from table where index >=1 and index < total_nums/ (int(total_nums/ rows) – 1) + 1。每個分塊可以分到不同的線程,所以即便同一個表dump都可以很快加速。
ps:這個項(xiàng)目大量使用glib(gnome)比較少見,看了一下glib doc覺得glib設(shè)計(jì)的挺好的用起來很方便,否則實(shí)現(xiàn)一個消息隊(duì)列加多線程還是要幾百行代碼的。接下來要看mydumper0.50的代碼。
作者: hoterran
原文鏈接地址:http://www.hoterran.info/mydumper_usage
myumper源碼下載地址:http://www.mydumper.org/?p=23