mysqldump支持使用-w,--where参数为要dump的表定义条件,但这个条件是全局的。也就是说,无论你要dump的是单表,还是多表,所有的表都使用这个条件。
如果我们要dump多个表,并且不同的表要使用不同的条件,或者是仅想为dump中,某些表指定条件的情况下,dump就无法做到了。如果不考虑数据一致性,那么还可以根据条件分别导出各表。如果要保持所有表的数据一致性,那么除了分批,还要考虑追加dump过程中产生的binlog日志,这就比较头大了。
最近因为业务的需要,也遇到了需要在保证数据一致性dump的情况下,为dump中的某个表定义条件的需求。思来想去,还是把手伸向了mysqldump源码。
考虑到自己的C语言还停留在几十年前的trubo c时代,通过源码改造实现一个复杂的条件定义不现实,而且使用上似乎也会变得困难,所以还是老套路,复杂的事让数据库去做,计划通过数据库存储过程去实现条件的定义,mysqldump通过调用存储过程去获得每个表的条件。
具体在mysqldump的源代码改造体现为:为-w,-where的值增加了一个判断,如果以#打头,表示后面跟的是存储过程,myqldump调用这个存储过程去获得表的条件,存储过程包含dbname、tbname两个参数,这样存储过程可以根据这两个表名知道要为那个表提供条件。如果不是以#打头,则与原始的mysqldump保持相同的处理逻辑。
具体的mysqldump源码修改如下(基于5.7.35版本,文件:client-->mysqldump.c):
1、目前版本中,-w,-where的值对应的变量是:static char *where,目前仅在函数:static void dump_table(char *table, char *db)中使用,所以在这个函数前加了一个函数,用来实现自定义的条件(也就是根据#标记判断是使用原始条件,还是调用存储过程获取条件)
static char *zj_dump_table_where(char *table, char *db){
if ( strncmp(where, "#", 1) == 0 ){
char query[QUERY_LENGTH];
MYSQL_ROW row;
MYSQL_RES *res;
char *mywhere=0;
my_snprintf(query, sizeof(query),
"call %s('%s', '%s')", where+1, db, table);
if (!mysql_query_with_error_report(mysql, &res, query)){
if ((row = mysql_fetch_row(res))){
mywhere = row ? (char*)row[0] : NULL;
}
do{
res = mysql_use_result(mysql);
mysql_free_result(res);
}while (!mysql_next_result(mysql));
}
return(mywhere);
}
return(where);
}
2、修改函数 static void dump_table(char *table, char *db),将其中使用where的地方改成使用步骤1定义的函数,我的做法是先在函数最前方的变量定义中定义了一个mywhere,并调用步骤1的条件取到条件,然后将使用where的地方改为mywhere。
static void dump_table(char *table, char *db)
{
char *mywhere=0; // 定义自己的变量,后续使用 where 的地方改为使用 mywhere
char ignore_flag;
char buf[200], table_buff[NAME_LEN+3];
...........................................
// 调用自定义获取 where 条件
if (where){
mywhere=zj_dump_table_where(table, db);
}
...........................................
dynstr_append_checked(&query_string, " FROM ");
dynstr_append_checked(&query_string, result_table);
// 改为使用 mywhere
// if (where)
// {
// dynstr_append_checked(&query_string, " WHERE ");
// dynstr_append_checked(&query_string, where);
// }
if (mywhere){
dynstr_append_checked(&query_string, " WHERE ");
dynstr_append_checked(&query_string, mywhere);
}
...........................................
}
else
{
my_bool freemem= FALSE;
char const* text= fix_identifier_with_newline(result_table, &freemem);
print_comment(md_result_file, 0, "\n--\n-- Dumping data for table %s\n--\n",
text);
if (freemem)
my_free((void*)text);
dynstr_append_checked(&query_string, "SELECT /*!40001 SQL_NO_CACHE */ * FROM ");
dynstr_append_checked(&query_string, result_table);
// 改为使用 mywhere
// if (where)
// {
// freemem= FALSE;
// text= fix_identifier_with_newline(where, &freemem);
// print_comment(md_result_file, 0, "-- WHERE: %s\n", text);
// if (freemem)
// my_free((void*)text);
// dynstr_append_checked(&query_string, " WHERE ");
// dynstr_append_checked(&query_string, where);
// }
if (mywhere){
freemem= FALSE;
text= fix_identifier_with_newline(mywhere, &freemem);
print_comment(md_result_file, 0, "-- WHERE: %s\n", text);
if (freemem)
my_free((void*)text);
dynstr_append_checked(&query_string, " WHERE ");
dynstr_append_checked(&query_string, mywhere);
}
...........................................
} /* dump_table */
数据库中的存储过程类似下面这样定义,这个存储过程表示 为:test.t1表使用条件:id>20,为test.t2表使用条件:host='%’,其他表返回空结果集,表示没有条件。
CREATE PROCEDURE p_dump_filter(
dbname text,
tbname text
)BEGIN
IF dbname='test' AND tbname='t1' THEN
SELECT 'id>20';
ELSEIF dbname='test' AND tbname='t2' THEN
SELECT 'host=''%''';
END IF;
SELECT NULL LIMIT 0; --- 这个是必须的,存储过程至少需要返回一个结果集
END;
当我们需要使用这个改造后的mysqldump时,如果不需要为不同的表定义导出条件,我们只需要按照原始的方式调用即可。如果需要为不同的表定义不同的条件,则先在数据库中创建好存储过程,然后使用参数 -w"#db_name.proc_name" 即可(db_name、proc_name需要替换为实际的库名与存储过程名)。
值得一提的是,在改造源码去实现db过滤(另一个功能改造)的时候,我还发现了一段有意思的代码,在dump_all_databases这个函数中。它把我之前发布的博文《mysqldump的几个坑》中的二号坑给填上了
if (mysql_db_found || (!my_strcasecmp(charset_info, row[0], "mysql")))
{
if (dump_all_tables_in_db(row[0]))
result=1;
mysql_db_found = 1;
【本文在个人微信公共号ZJCXC上同步发表】