状态描述:
公司的某个系统的数据库疏于维护,今天(2014/8/25)有个项目组PHPer不小心把线上的Mysql主库的某个表truncate掉了,询问是否有办法恢复数据.
恰好不巧这个唯独这个数据库从未做过备份,由于truncate(不同于delete)是不记录日志的,看来这次要悲剧了.后又与PHPer沟通了解到,原来这个项目新上不到一个月,好像又有了一线希望,马上设置好定时备份该数据表的计划任务后开始着手恢复.
恢复思路:尝试在测试库中通过mysql的二进制日志重放日志来恢复数据,然后稍加处理导入到生产数据库中;
step 1:查看数据库备份(本例没有备份文件)和二进制文件能否拼凑出完整的数据;
查看是否开启二进制日志:
mysql> show variables like 'log_bin';
+---------------+-------+
| Variable_name | Value |
+---------------+-------+
| log_bin | ON |
+---------------+-------+
1 row in set (0.00 sec)
查看mysql binarylog的保存周期;
mysql> show variables like 'expire_logs_days';
+------------------+-------+
| Variable_name | Value |
+------------------+-------+
| expire_logs_days | 30 |
+------------------+-------+
1 row in set (0.00 sec)
本地磁盘空间足够,并未清理日志文件;
数据库已开启二进制日志,并且设置的过期日期是30天且未被清理,由此推断被truncate的数据仍完整的存在于二进制文件中,这是本次通过mysqlbinlog进行恢复的前提.
step 2:查找需要使用的binary log文件,以及恢复的start-position(或者start-datetime)和end-position(或者stop-position)
mysqlbinlog有5个常用的参数:
-d:指定数据库名
--start-position:指定开始位置,
--stop-position:指定结束为止
--start-datetime:指定开始时间,格式如"2010-01-07 11:25:56"
--stop-position:指定结束时间
本例实际情况涉及到的文件列表如下:
-rw-rw---- 1 mysql mysql 1073760033 Aug 20 18:04 mysql-bin.002333
-rw-rw---- 1 mysql mysql 1073741925 Aug 21 11:30 mysql-bin.002334
-rw-rw---- 1 mysql mysql 1073741919 Aug 22 06:00 mysql-bin.002335
-rw-rw---- 1 mysql mysql 1073741936 Aug 22 22:30 mysql-bin.002336
-rw-rw---- 1 mysql mysql 1073741995 Aug 23 16:00 mysql-bin.002337
-rw-rw---- 1 mysql mysql 1073742017 Aug 24 09:00 mysql-bin.002338
-rw-rw---- 1 mysql mysql 1073742030 Aug 25 00:00 mysql-bin.002339
-rw-rw---- 1 mysql mysql 281269618 Aug 25 18:30 mysql-bin.002340
开始位置的确定:本例中情况比较特殊,开始日期设置为项目创建日期之前的任意时间点即可.
结束为止的确定:结束位置一定要特别注意,结束position一定要设置为truncate语句的上一条的结束位置,保证数据完整的同时又不能执行truncate语句.具体查找方法如下:
shell>mysqlbinlog -d db_mobi mysql-bin.002340 | grep -A5 -B 5 -E "truncate|agent_select_resblock_detail"
输出如下:
--
BEGIN
/*!*/;
# at 618699837
#140825 11:12:05 server id 1 end_log_pos 618699946 Query thread_id=258628603 exec_time=0 error_code=0
SET TIMESTAMP=1408936325/*!*/;
TRUNCATE agent_select_resblock_detail
/*!*/;
# at 618699946
#140825 11:12:05 server id 1 end_log_pos 618699973 Xid = 9541176706
COMMIT/*!*/;
# at 618699973
由此可以确定truncate操作日期大约在11:12:05
进一步确定:
shell>mysqlbinlog -d homelink_mobi mysql-bin.002340 --start-datetime "2014-08-25 11:12:00" --stop-datetime="2014-08-25 11:12:10"
输出:
...(省略)...
# at 618699733
#140825 11:12:03 server id 1 end_log_pos 618699760 Xid = 9541176633
COMMIT/*!*/;
# at 618699760
#140825 11:12:05 server id 1 end_log_pos 618699837 Query thread_id=258628603 exec_time=0 error_code=0
SET TIMESTAMP=1408936325/*!*/;
BEGIN
/*!*/;
# at 618699837
#140825 11:12:05 server id 1 end_log_pos 618699946 Query thread_id=258628603 exec_time=0 error_code=0
SET TIMESTAMP=1408936325/*!*/;
TRUNCATE agent_select_resblock_detail
/*!*/;
# at 618699946
#140825 11:12:05 server id 1 end_log_pos 618699973 Xid = 9541176706
COMMIT/*!*/;
# at 618699973
...(省略)...
由此可以得出结束position为618699760;
step 3:导出生产环境中的表结构,处理自增长字段(如果需要),并将表结构导入到测试数据库中;
首先查看被误truncate的表结构是否有自增长字段:
mysql> desc agent_select_resblock_detail;
+---------------+--------------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+---------------+--------------+------+-----+-------------------+-----------------------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| agentId | varchar(20) | NO | | NULL | |
| communityCode | varchar(20) | YES | | NULL | |
| communityName | varchar(256) | NO | | NULL | |
| dateTime | timestamp | YES | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
| agentType | int(2) | NO | | NULL | |
+---------------+--------------+------+-----+-------------------+-----------------------------+
6 rows in set (0.00 sec)
其中id字段为自增长字段.
注意:本次恢复的过程只需要被误truncate的表所属数据库的表结构,并设定取消自增长;
shell>mysqldump -uroot -p -d db_mobi --no-data | sed 's/ AUTO_INCREMENT=[0-9]*\b//' > biaojiegou.sql
并传递到测试数据库机器:
scp biaojiegou.sql Test_db_IP:/root/
step 4:在测试数据库中创建db-mobi数据库并导入表结构;
mysql>drop database db_mobi;
mysql>create database db_mobi;
mysql> use db_mobi;
mysql> source /root/biaojiegou.sql;
至此,数据表结构导入完毕.
step4:使用mysqlbinlog将日志导入到测试数据库中;
shell>mysqlbinlog -d db_mobi mysql-bin.002333 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002334 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002335 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002336 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002337 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002338 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002339 |mysql -uroot -ppassword
shell>mysqlbinlog -d db_mobi mysql-bin.002340 --stop-position 618699760 |mysql -uroot -ppassword
mysql-bin.00233[3-9]的开始时间和结束时间在本例中可以省略,其中最后一条命令中position由第二步得到.
导入完毕后查看被误truncate的表数据自增长字段最小值和最大值:
mysql> select min(id) from db_mobi.agent_select_resblock_detail;
+---------+
| min(id) |
+---------+
| 1 |
+---------+
1 row in set (0.00 sec)
mysql> select max(id) from db_mobi.agent_select_resblock_detail;
+---------+
| max(id) |
+---------+
| 2593 |
+---------+
1 row in set (0.01 sec)
子增长字段由1增长到2593;
step5:step4结束后,将被误truncate表的数据导出(注意:只导数据,不导出表结构,参数-t)
mysqldump -uroot -p -t db_mobi agent_select_resblock_detail >/root/db_mobil.agent_select_resblock_detail.sql
将导出的数据传送到生产服务器上(如/root/目录)(略)
step6:导入生产环境时候处理生产环境中的子增长字段,并导入step5的表数据;
因为db_mobil.agent_select_resblock_detail表被truncate过,所以子增长字段最小值应该为1,这时候需要把生产环境的自增长字段增加2593(step 4得出)
mysql> update db_mobi.agent_select_resblock_detail set id=id+2593;
Query OK, 2593 rows affected (0.10 sec)
Rows matched: 2593 Changed: 2593 Warnings: 0
将测试数据库中导出的表数据导入生产环境;
mysql> use db_mobi;
mysql> source /root/db_mobil.agent_select_resblock_detail.sql;
此时,数据表恢复完毕.最后再做一些必要的检查.
可能遇见的错误:
1、mysqlbinlog导入数据时提示:/usr/bin/mysqld: unknown variable 'default-character-set=utf8' ;
这个错误可能是mysqlbinlog的一个错误,临时注释掉/etc/my.cnf中default-character-set = utf8行(不需要重启数据库),待导入完毕后取消注释即可.
2、mysqlbinlog导入数据时提示类似:table_name does not exists ;
需要注意linux中对mysql表名的大小写是敏感的
3、mysqlbinlog导入到测试数据被truncate的表数据仍为空;
需要注意,mysqlbinlog导入数据的时候的结束的position一定要在truncate的上一条语句的结束点(一定不能包括truncate这条语句).
4、提示auto_increment相关错误;
需要注意在生产数据库导出表结构后需要删除auto_increment或者设置auto_increment=0
5、测试数据库导出的数据导入到生产环境后,生产环境中原有表数据丢失;
需要注意测试数据库中导出数据时不要带表结构,带带表结构导出会在导入到生产库的时候先drop table;
参考资料:
http://melikedev.com/2011/06/01/mysql-remove-auto_increment-from-schema-dumps-mysqldump/
linux:man mysqlbinlog