mysql闪回实战-binlog2sql
--如果想下载相应的word文档的话,则可以点击这里下载。
一:闪回原理
binlog以event形式记录了所有的变更信息,因此我们把需要回滚的event,从后往前回滚回去即可。对于delete操作,我们从binlog提取出delete信息,生成的回滚语句是insert。
真实的闪回场景中,最关键的是能快速筛选出真正需要回滚的SQL。
我们使用开源工具binlog2sql来进行实战演练。
二:环境搭建
2.1 前提条件
MySQL server必须设置以下参数:
[mysqld]
server_id = 1
log_bin = /var/log/mysql/mysql-bin.log
max_binlog_size = 1G
binlog_format = row
binlog_row_image = full
已测试环境
Python 2.7, 3.4+
MySQL 5.6, 5.7
pip 9.0.1
2.2 安装binlog2sql
2.2.1 下载binlog2sql
有两种方式可选。
如果服务器不能联网,则选择第二种方式(先在本地电脑下载安装包,再上传到服务器)。
2.2.1.1 联网安装
shell> git clone https://github.com/danfengcao/binlog2sql.git && cd binlog2sql
2.2.1.2 下载上传安装包
点击https://download.csdn.net/download/YABIGNSHI/11986031
下载binlog2sql-master.zip压缩包
unzip binlog2sql-master.zip
mv binlog2sql-master binlog2sql
cd binlog2sql
2.2.2 安装binlog2sql
shell> pip install -r requirements.txt
#假如报错:No package pip available,需要安装pip:
方式1:
yum install python-pip,pip install --upgrade pip
方式2:
进入网站https://pypi.python.org/pypi/pip下载
解压tar -xvf 安装包
cd 加压后的目录
python setup.py install
假如执行pip install -r requirements.txt报错:
mportError: No module named pkg_resources
这个问题通常是由于升级到python2.7后执行pip产生的,解决方案是重新在python2.7环境中安装pip。
假如执行pip install -r requirements.txt报错:
Could not find a version that satisfies the requirement pymysql (from versions: )
No matching distribution found for pymysql
需要安装pymysql。
安装步骤可以参考:https://blog.csdn.net/SuperBoy_Liang/article/details/89335906
三 相关参数介绍
3.1 mysql连接配置
-h host; -P port; -u user; -p password
3.2 解析模式
--stop-never 持续解析binlog。可选。默认False,同步至执行命令时最新的binlog位置。
-K, --no-primary-key 对INSERT语句去除主键。可选。默认False
-B, --flashback 生成回滚SQL,可解析大文件,不受内存限制。可选。默认False。与stop-never或no-primary-key不能同时添加。
--back-interval -B模式下,每打印一千行回滚SQL,加一句SLEEP多少秒,如不想加SLEEP,请设为0。可选。默认1.0。
3.3 解析范围控制
--start-file 起始解析文件,只需文件名,无需全路径 。必须。
--start-position/--start-pos 起始解析位置。可选。默认为start-file的起始位置。
--stop-file/--end-file 终止解析文件。可选。默认为start-file同一个文件。若解析模式为stop-never,此选项失效。
--stop-position/--end-pos 终止解析位置。可选。默认为stop-file的最末位置;若解析模式为stop-never,此选项失效。
--start-datetime 起始解析时间,格式'%Y-%m-%d %H:%M:%S'。可选。默认不过滤。
--stop-datetime 终止解析时间,格式'%Y-%m-%d %H:%M:%S'。可选。默认不过滤。
3.4 对象过滤
-d, --databases 只解析目标db的sql,多个库用空格隔开,如-d db1 db2。可选。默认为空。
-t, --tables 只解析目标table的sql,多张表用空格隔开,如-t tbl1 tbl2。可选。默认为空。
--only-dml 只解析dml,忽略ddl。可选。默认False。
--sql-type 只解析指定类型,支持INSERT, UPDATE, DELETE。多个类型用空格隔开,如--sql-type INSERT DELETE。可选。默认为增删改都解析。用了此参数但没填任何类型,则三者都不解析。
四 闪回实战
4.1 闪回步骤简单介绍
① 先根据库、表、时间做一次过滤,筛选出真正需要回滚的SQL。
如:
python /download/binlog2sql/binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054' --start-datetime='2016-12-26 11:44:00' --stop-datetime='2016-12-26 11:50:00' > /tmp/raw.sql
备注:
--sql-type 只解析指定类型,支持INSERT, UPDATE, DELETE。多个类型用空格隔开,如--sql-type INSERT DELETE。可选。默认为增删改都解析。用了此参数但没填任何类型,则三者都不解析。
② 根据position进一步过滤
上一步会产生相关sql。sql后有相应的起始position.
③ 生成闪回语句
shell> python binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054'--start-position=257427 --stop-position=504272 -B > /tmp/rollback.sql
④:确认无误后,执行闪回语句
4.2 示例
背景:小明在11:44时误删了test库user表大批的数据,需要紧急回滚。
test库user表原有数据
mysql> select *from user;
+----+--------+---------------------+
| id | name | addtime |
+----+--------+---------------------+
| 1 | 小赵 | 2013-11-1100:04:33 |
| 2 | 小钱 | 2014-11-1100:04:48 |
| 3 | 小孙 | 2016-11-1120:25:00 |
| 4 | 小李 | 2013-11-1100:00:00 |
.........
+----+--------+---------------------+
16384rows inset(0.04sec)
11:44时,user表大批数据被误删除。与此同时,正常业务数据是在继续写入的
mysql> delete from user where addtime>'2014-01-01';
Query OK,16128rows affected(0.18sec)
mysql> select count(*)from user;
+----------+
| count(*) |
+----------+
| 261 |
+----------+
恢复数据步骤:
登录mysql,查看目前的binlog文件
mysql> show master logs;
+------------------+-----------+
| Log_name | File_size |
+------------------+-----------+
| mysql-bin.000053 | 168652863 |
| mysql-bin.000054 | 504549 |
+------------------+-----------+
最新的binlog文件是mysql-bin.000054。我们的目标是筛选出需要回滚的SQL,由于误操作人只知道大致的误操作时间,我们首先根据时间做一次过滤。只需要解析test库user表。(注:如果有多个sql误操作,则生成的binlog可能分布在多个文件,需解析多个文件)
shell> Python binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054'--start-datetime='2016-12-26 11:44:00' --stop-datetime='2016-12-26 11:50:00' > /tmp/raw.sql
raw.sql输出:
DELETE FROM`test`.`user`WHERE`addtime`='2014-11-11 00:04:48'AND`id`=2AND`name`='小钱'LIMIT1;#start 257427 end 265754 time 2016-12-26 11:44:56
DELETE FROM`test`.`user`WHERE`addtime`='2015-11-11 20:25:00'AND`id`=3AND`name`='小孙'LIMIT1;#start 257427 end 265754 time 2016-12-26 11:44:56
...
DELETE FROM`test`.`user`WHERE`addtime`='2016-12-14 23:09:07'AND`id`=24530AND`name`='tt'LIMIT1;#start 257427 end 504272 time 2016-12-26 11:44:56
INSERT INTO`test`.`user`(`addtime`,`id`,`name`)VALUES('2016-12-10 00:04:33',32722,'小王');#start 504299 end 504522 time 2016-12-26 11:49:42
...
/*
注意:假如执行的时候报错:
Traceback (most recent call last):
File "binlog2sql/binlog2sql.py", line 125, in
binlog2sql.process_binlog()
File "binlog2sql/binlog2sql.py", line 70, in process_binlog
for binlogevent in stream:
File "/usr/lib/python2.6/site-packages/pymysqlreplication/binlogstream.py", line 370, in fetchone
pkt = self._stream_connection._read_packet()
File "/usr/lib/python2.6/site-packages/pymysql/connections.py", line 983, in _read_packet
packet.check_error()
File "/usr/lib/python2.6/site-packages/pymysql/connections.py", line 395, in check_error
err.raise_mysql_exception(self._data)
File "/usr/lib/python2.6/site-packages/pymysql/err.py", line 107, in raise_mysql_exception
raise errorclass(errno, errval)
pymysql.err.InternalError: (1236, u'Misconfigured master - server id was not set')
可以通过set global server_id=N;然后再次执行该语句即可。
*/
根据位置信息,我们确定了误操作sql来自同一个事务,准确位置在257427-504272之间(binlog2sql对于同一个事务会输出同样的start position)。再根据位置过滤,使用 -B 选项生成回滚sql,检查回滚sql是否正确。(注:真实场景下,生成的回滚SQL经常会需要进一步筛选。结合grep、编辑器等)
shell> python binlog2sql/binlog2sql.py -h127.0.0.1 -P3306 -uadmin -p'admin' -dtest -tuser --start-file='mysql-bin.000054'--start-position=257427 --stop-position=504272 -B > /tmp/rollback.sql
rollback.sql输出:
INSERT INTO`test`.`user`(`addtime`,`id`,`name`)VALUES('2016-12-14 23:09:07',24530,'tt');#start 257427 end 504272 time 2016-12-26 11:44:56
INSERT INTO`test`.`user`(`addtime`,`id`,`name`)VALUES('2016-12-12 00:00:00',24529,'小李');#start 257427 end 504272 time 2016-12-26 11:44:56
...
INSERT INTO`test`.`user`(`addtime`,`id`,`name`)VALUES('2014-11-11 00:04:48',2,'小钱');#start 257427 end 265754 time 2016-12-26 11:44:56
shell> wc -l /tmp/rollback.sql
16128 /tmp/rollback.sql
与业务方确认回滚sql没问题,执行回滚语句。登录mysql,确认回滚成功。
shell> mysql -h127.0.0.1 -P3306 -uadmin -p'admin' < /tmp/rollback.sql
mysql> select count(*)from user;
+----------+
| count(*) |
+----------+
| 16389 |
+----------+
五 注意事项
① 无法回滚truncate table
② 无法回滚DDL
试验发现,若某个表被drop了,则该表在此之前的DML操作也将无法回滚(只能看到相应的建表删表语句,看不到DML语句)
③ 一次删除多条记录的delete sql会被生成N(N=被删除的记录条数)条delete sql(因为binlog日志里就是这样记录的),但是同一批操作里的start和end postion是一样的,不影响恢复。
示例: