• gdb调试mydumper排查警告信息根因


    问题背景:

    在使用 mydumepr 0.9.5 版本备份云上数据库时,偶尔会有告警信息,几乎都是 sys 库的,其实也没有备份这个库,不知道为什么会去检测 sys 库,并且还有告警信息。

    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary
    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary_by_file_io
    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary_by_file_io_type
    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary_by_stages
    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary_by_statement_latency
    
    ** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary_by_statement_type
    
    

    这个问题在备份某个RDS FOR MYSQL5.7 实例时,可以稳定复现,想尝试从源码中寻找一些答案。

     
     
     

    1、下载源码到本地,并切换版本

    github地址:https://github.com/mydumper/mydumper
    
    在本地克隆代码:
    git clone https://github.com/mydumper/mydumper.git
    切换版本
    git checkout v0.9.5
    参考官方文档编译安装
    

     
     
     

    2、查看源代码

    mydumper 整体上看应该不算太难,几乎只看 mydump.c 文件即可。
    全篇搜索关键字【Broken table detected】,发现在【dump_database】函数的一个 while 循环中,当 is_view 为假 ,并且 row[ecol ] 为空,才会触发这个告警信息。

    	while ((row = mysql_fetch_row(result))) {
    
    .... //省略
    		/* Check for broken tables, i.e. mrg with missing source tbl */
    		if ( !is_view && row[ecol] == NULL ) {
    			g_warning("Broken table detected, please review: %s.%s", database, row[0]);
    			dump = 0;
    		}
    
    ..... // 省略部分代码
    }
    

    继续排查函数间调用关系,
    main()
    -->start_dump()
    -->dump_database() //备注:会执行 show table status 获取所有表名

    start_dump() 涉及备份数据库的代码:

    		while ((row=mysql_fetch_row(databases))) {
    			if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    				continue;
    			dump_database(conn, row[0], nufile, &conf);
    			/* Checks PCRE expressions on 'database' string */
    			if (!no_schemas && (regexstring == NULL || check_regex(row[0],NULL)))
    				dump_create_database(conn, row[0]);
    
    		}
    

    小结:从函数调用代码看,即使我在mydumper时指定了备份的库表,但在正式备份数据前并没有做过滤,对所有表都会做一些预检。

     
     
     

    3、开始 GDB 调试源代码

    3.1 安装GDB命令

    yum install gdb

    3.2 常用命令
    list 行号  --查看指定位置的行号
    
    b 行号   --设置到断点的行号
    r  --运行程序,如果是带参的可以这样写  r  -h 127.0.0.1 -P 3357 -t 1 -W -u root -p 'c123456'
    n   --单步运行,不进行函数内部
    s   --单步运行,进入函数内部
    info threads  查看所有线程
    thread 线程ID
    print --打印变量值
    info break  --查看所有断点
    del xx  --删除断点
    continue  --跳到下一个断点处
    display 在每次暂停的时候输出表达式的值
    
    3.3 开始调试

    3.3.1
    进入GDB 模式

    root@:~/czg/mydumper# gdb mydumper
    GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.5) 7.11.1
    ......
    Type "apropos word" to search for commands related to "word"...
    Reading symbols from mydumper...done.
    (gdb)
    
    

    3.3.2
    打一个断点,在 1452 行处,因为 dump_database 在循环中

    1452		while ((row=mysql_fetch_row(databases))) {
    1453			if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    1454				continue;
    1455			dump_database(conn, row[0], nufile, &conf);
    
    

    3.3.3
    运行程序,重点关注 【(gdb)】行内容,这些是我输入的命令,
    进入到循环之内,第一个库并不是sys库,输入c 进入下一次循环,因为我们在 1452 行号有断点,所以还是会停下,继续查看是否是 sys 库,直接到遇到 sys 库。

    (gdb) b 1452
    Breakpoint 1 at 0x41dad9: file /tmp/mydumper/mydumper.c, line 1452.
    (gdb) r -h rr-xxaliyuncs.com -P 3306 -t 1 -W -u -p '' -s 2000000 --regex '^(test\.$)' -c -o /root/test
    Starting program: /usr/bin/mydumper -h rr-xxxaliyuncs.com -P 3306 -t 1 -W -u -p '' -s 2000000 --regex '^(test\.$)' -c -o /root/test
    [Thread debugging using libthread_db enabled]
    Using host libthread_db library "/lib/x86_64-linux-gnu/libthread_db.so.1".
    [New Thread 0x7ffff56cc700 (LWP 12599)]
    
    Thread 1 "mydumper" hit Breakpoint 1, start_dump (conn=0x9f16c0) at /tmp/mydumper/mydumper.c:1452
    warning: Source file is more recent than executable.
    1452                    while ((row=mysql_fetch_row(databases))) {
    (gdb) n
    1453                            if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    (gdb) print row[0]
    $1 = 0x9ff598 "information_schema"
    (gdb) c
    Continuing.
    
    Thread 1 "mydumper" hit Breakpoint 1, start_dump (conn=0x9f16c0) at /tmp/mydumper/mydumper.c:1452
    1452                    while ((row=mysql_fetch_row(databases))) {
    (gdb) n
    1453                            if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    (gdb) print row[0]
    $2 = 0x9ff5d8 "test"
    (gdb)
    ......  //过程省略,直到遇到sys 库。
    
    

    遇到sys 库,进入到 dump_database 函数中

    Thread 1 "mydumper" hit Breakpoint 1, start_dump (conn=0x9f16c0) at /tmp/mydumper/mydumper.c:1452
    1452                    while ((row=mysql_fetch_row(databases))) {
    (gdb) n
    1453                            if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    (gdb) print row[0]
    $5 = 0x9ff680 "sys"
    (gdb) n
    1455                            dump_database(conn, row[0], nufile, &conf);
    (gdb) s
    dump_database (conn=0x9f16c0, database=0x9ff680 "sys", file=0x0, conf=0x7fffffffe3d0) at /tmp/mydumper/mydumper.c:1915
    1915    void dump_database(MYSQL * conn, char *database, FILE *file, struct configuration *conf) {
    (gdb)
    

    进入到dump_database 函数,开始单步运行,这段代码主要为了确定两个变量值
    ecol 结果中,列名为 Engine 为的下标位置,这个值后面会用到来确定内容。
    ccol 结果中,列名为 Comment 为的下标位置,这个值后面会用到来确定内容。
    最后ecol =1 ccol=17

    
    Thread 1 "mydumper" hit Breakpoint 1, start_dump (conn=0x9f16c0) at /tmp/mydumper/mydumper.c:1452
    1452                    while ((row=mysql_fetch_row(databases))) {
    (gdb) n
    1453                            if (!strcasecmp(row[0],"information_schema") || !strcasecmp(row[0], "performance_schema") || (!strcasecmp(row[0], "data_dictionary")))
    (gdb) print row[0]
    $5 = 0x9ff680 "sys"
    (gdb) n
    1455                            dump_database(conn, row[0], nufile, &conf);
    (gdb) s
    dump_database (conn=0x9f16c0, database=0x9ff680 "sys", file=0x0, conf=0x7fffffffe3d0) at /tmp/mydumper/mydumper.c:1915
    1915    void dump_database(MYSQL * conn, char *database, FILE *file, struct configuration *conf) {
    (gdb) n
    1919            mysql_select_db(conn,database);
    (gdb) n
    1920            if (detected_server == SERVER_TYPE_MYSQL)
    (gdb) print detected_server == SERVER_TYPE_MYSQL
    $6 = 1
    (gdb) n
    1921                    query= g_strdup("SHOW TABLE STATUS");
    (gdb) n
    1925            if (mysql_query(conn, (query))) {
    (gdb) n
    1926                    g_critical("Error: DB: %s - Could not execute query: %s", database, mysql_error(conn));
    (gdb) n
    1925            if (mysql_query(conn, (query))) {
    (gdb) n
    1932            MYSQL_RES *result = mysql_store_result(conn);
    (gdb) n
    1935            int ecol= -1, ccol= -1;
    (gdb) n
    1933            MYSQL_FIELD *fields= mysql_fetch_fields(result);
    (gdb) n
    1932            MYSQL_RES *result = mysql_store_result(conn);
    (gdb) n
    1935            int ecol= -1, ccol= -1;
    (gdb) n
    1936            for (i=0; i<mysql_num_fields(result); i++) {
    (gdb) n
    1933            MYSQL_FIELD *fields= mysql_fetch_fields(result);
    (gdb) n
    1936            for (i=0; i<mysql_num_fields(result); i++) {
    (gdb) n
    1937                    if (!strcasecmp(fields[i].name, "Engine")) ecol= i;
    (gdb) n
    1938                    else if (!strcasecmp(fields[i].name, "Comment")) ccol= i;
    (gdb) n
    1936            for (i=0; i<mysql_num_fields(result); i++) {
    (gdb) n
    1937                    if (!strcasecmp(fields[i].name, "Engine")) ecol= i;
    (gdb) n
    1936            for (i=0; i<mysql_num_fields(result); i++) {
    (gdb) n
    1937                    if (!strcasecmp(fields[i].name, "Engine")) ecol= i;
    

    继续向下,开始进入到 while 中,这个循环中(这是个截图)

    再设置一个断点 b 【b 1948】,让程序进入到这个 while 循环中,这个循环中 is_view 初值是0
    同时,这条记录的name是 host_summary ,也就是表名。

    ecol =1
    ccol=17
    这是上次看到的变量值,对于查看变量值很关键
    从第一个if 看,
    detected_server == SERVER_TYPE_MYSQL //为True
    row[17] 的值是 【0xa0393d "execute command denied to user 'dbbackup'@'%' for routine 'sys.format_time'"】 // 这个不符合【( row[ccol] == NULL || !strcmp(row[ccol],"VIEW") )】条件,所以 is_view变量值还是0

    再看第二个if,
    ( !is_view && row[ecol] == NULL )
    // !is_view 条件成立 ,row[1] 也确实是空值,条件成立 ,最终会打出告警信息
    【** (mydumper:19312): WARNING **: Broken table detected, please review: sys.host_summary】

    (gdb) b 1948
    Breakpoint 2 at 0x4193a8: file /tmp/mydumper/mydumper.c, line 1948.
    (gdb) c
    Continuing.
    
    Thread 1 "mydumper" hit Breakpoint 2, dump_database (conn=0x9f16c0, database=<optimized out>, file=0x0, conf=0x7fffffffe3d0) at /tmp/mydumper/mydumper.c:1948
    1948            while ((row = mysql_fetch_row(result))) {
    (gdb) n
    1958                    if ((detected_server == SERVER_TYPE_MYSQL) && ( row[ccol] == NULL || !strcmp(row[ccol],"VIEW") ))
    (gdb) print row[0]
    $2 = 0xa03930 "host_summary"
    (gdb) print row[1]
    $3 = 0x0
    (gdb) print row[17]
    $4 = 0xa0393d "execute command denied to user 'dbbackup'@'%' for routine 'sys.format_time'"
    (gdb) print detected_server == SERVER_TYPE_MYSQL
    $5 = 1
    (gdb) print is_view
    $6 = 0
    (gdb) n
    1962                    if ( !is_view && row[ecol] == NULL ) {
    (gdb) print is_view
    $7 = 0
    

    总结:

    报错原因是mydumper切换到sys库,并且执行show table status命令获取所有表信息,并且获取Engine、Comment 这两个列的信息,并用Comment这个列的信息来判断是否是视图,如果为空值或者内容值是 VIEW ,则标记为视图,如果Comment列信息不是这些内容,并且Engine列的内容为空,则告警这个视图是损坏的。
    问题的关键是show table status 结果中Comment的内容是【execute command denied to user 'dbbackup'@'%' for routine 'sys.format_time'】
    所以mydumper认为这不是一个视图
    我们使用的备份账号权限都是一样的,但只有比较早的RDS实例会有出现告警信息,应该是云上RDS小版本的原因,每次的权限范围或其它显示方面不太一样,最终导致了mydumper告警的原因。

  • 相关阅读:
    [App Store Connect帮助]八、维护您的 App(5)生成产品报告
    [App Store Connect帮助]八、维护您的 App(4.4)重置 App 总评分(iOS、Apple TVOS、macOS)
    [App Store Connect帮助]八、维护您的 App(4.3)回复顾客评论(iOS、macOS 或 watchOS)
    [App Store Connect帮助]八、维护您的 App(4.2)查看评分与评论
    Agent XPs disable
    (2.4)DDL增强功能-数据汇总grouping、rollup、cube
    (2.3)DDL增强功能-流程化控制与动态sql
    (2.2)DDL增强功能-自定义函数/表值函数与存储过程
    (2.1)sql server数据类型、同义词、分区表
    (1.5)DML增强功能-try catch及事务控制
  • 原文地址:https://www.cnblogs.com/nanxiang/p/16118063.html
Copyright © 2020-2023  润新知