RDS的备份工具用的是 Percona-XtraBackup(后面简称PXB),这个工具包里有2个重要的工具,innobackupex和xtrabackup,后者是C编译出的二进制文件,负责备份 InnoDB 数据,前者是一个Perl 脚本,对后者进行封装,同时负责备份非 InnoDB 数据。xtrabackup 二进制里内嵌了InnoDB引擎,所以能很好的处理InnoDB数据。在2.2版本之前,PXB 分别针对不同版本的 MySQL 源码(5.1/5.5/5.6)编译了不同版本的xtrabackup,以便备份不同版本的MySQL数据,然而在2.2之后,PXB官方觉得5.6已经可以很好的兼容5.1和5.5,所以就只针对 5.6.22 版本的代码编译了一个 xtrabackup 二进制文件,关于这个改动可以看官方的BP #single binary。故事就由此发生。。。
当我们对5.5版本的备份集进行还原的时候,xtrabackup crash 了,报错信息如下:
- 2015-05-12 19:03:08 7fd9d8258720 InnoDB: Assertion failure in thread 140573610968864 in file pars0pars.cc line 865
- InnoDB: Failing assertion: sym_node->table != NULL
- InnoDB: We intentionally generate a memory trap.
- InnoDB: Submit a detailed bug report to http://bugs.mysql.com.
- InnoDB: If you get repeated assertion failures or crashes, even
- InnoDB: immediately after the mysqld startup, there may be
- InnoDB: corruption in the InnoDB tablespace. Please refer to
- InnoDB: http://dev.mysql.com/doc/refman/5.6/en/forcing-innodb-recovery.html
- InnoDB: about forcing recovery.
- 11:03:08 UTC - xtrabackup got signal 6 ;
- This could be because you hit a bug or data is corrupted.
- This error can also be caused by malfunctioning hardware.
- We will try our best to scrape up some info that will hopefully help
- diagnose the problem, but since we have already crashed,
- something is definitely wrong and this may fail.
- Thread pointer: 0x176aeb0
- Attempting backtrace. You can use the following information to find out
- where mysqld died. If you see no messages after this, something went
- terribly wrong...
- stack_bottom = 0 thread_stack 0x10000
- xtrabackup(my_print_stacktrace+0x35) [0x9f5331]
- xtrabackup(handle_fatal_signal+0x2bb) [0x7f801b]
- /lib64/libpthread.so.0() [0x3530c0f500]
- /lib64/libc.so.6(gsignal+0x35) [0x35f40328a5]
- /lib64/libc.so.6(abort+0x175) [0x35f4034085]
- xtrabackup() [0x76bfb0]
- xtrabackup(pars_update_statement(upd_node_t*, sym_node_t*, void*)+0x30) [0x76c8d8]
- xtrabackup(yyparse()+0xcb1) [0xa5ef27]
- xtrabackup(pars_sql(pars_info_t*, char const*)+0xaf) [0x76e06d]
- xtrabackup(que_eval_sql(pars_info_t*, char const*, unsigned long, trx_t*)+0x85) [0x78eeb2]
- xtrabackup(row_drop_table_for_mysql(char const*, trx_t*, bool, bool)+0xa98) [0x720a0c]
- xtrabackup(row_mysql_drop_temp_tables()+0x24c) [0x721503]
- xtrabackup(recv_recovery_rollback_active()+0x2c) [0x753ebe]
- xtrabackup(innobase_start_or_create_for_mysql()+0x17aa) [0x7293c4]
- xtrabackup() [0x607a00]
- xtrabackup() [0x610204]
- xtrabackup(main+0x8b8) [0x611674]
- /lib64/libc.so.6(__libc_start_main+0xfd) [0x35f401ecdd]
- xtrabackup() [0x604369]
## bug 分析
从crash的信息
,可以定位的crash的代码点
- InnoDB: Assertion failure in thread 140573610968864 in file pars0pars.cc line 865
- 862| sym_node->table = dict_table_open_on_name(
- 863| sym_node->name, TRUE, FALSE, DICT_ERR_IGNORE_NONE);
- 864|
- 865| ut_a(sym_node->table != NULL);
可以看到,是 InnoDB 在试图打开一张表的时候,打开失败,直接 assert 了。
分析调用堆栈,是xtrabackup在恢复数据的时候,启动了内嵌的 InnoDB 引擎,在活跃事务回滚的时候,会将备份时候存在的临时表全部 drop 掉。在删除表的时候,除了要删除表本身,还需要删除在 InnoDB 系统表中的记录,删除记录是通过内部执行sql的方式做的
,其中有这么一段sql
- que_eval_sql
- 4195| "DELETE FROM SYS_TABLESPACES\n"
- 4196| "WHERE SPACE = space_id;\n"
- 4197| "DELETE FROM SYS_DATAFILES\n"
- 4198| "WHERE SPACE = space_id;\n"
SYS_TABLESPACES 和 SYS_DATAFILES 这2个系统表是在5.6中才有的,5.5是没有的,所以在调用
的时候就打不开 SYS_TABLESPACES 表,导致CRASH。
- dict_table_open_on_name
这里给一个简单的修复方法,就是在调用
删除记录前判断下当前数据是否是5.6的,如果是就传原来的sql,如果不是,就传去掉 SYS_TABLESPACES 和 SYS_DATAFILES 的sql。PXB官方已经确认这个bug #1399471,目前尚未修复,应该会在下个版本修掉。 如果等不及的话,可以有2种选择:
- que_eval_sql
从上面的分析可以得到,要触发这个bug,需要这些条件:
1. PXB版本是2.2以上
2. MySQL版本是5.5/5.1
3. 备份的时候有持有临时表的回话。
当满足上面这些条件,恢复数据就会crash。
PXB备份出来的idb数据时间点是不一致的,恢复数据时需要应用redo将数据追到一个一致的点,效果上就相当于 MySQL 异常关闭,buffer pool 有数据没有落盘,然后重启应用redo做崩溃恢复。所以这个场景只有在用PXB才容易出现,对正常使用的mysqld来说一般是不会的,因为我们在升级的时候都是正常关闭 mysqld,这是临时表都被清理干净了,后再用新版mysqld启动,所以新版mysqld启动时就不需要做drop临时表的操作,自然不会crash了。
来源: http://mysql.taobao.org/monthly/2015/05/03/