腾讯云机器上自建MySQL 上update操做时,忘加where条件 ,使用mysqlbinlog搭配sed命令完美还原
MySQL版本号:5.6.39;
mysql必须开启binlog,而且mysql的binlog最好是Row模式;
mysql数据库指定字符集位utf8,同时表的字符集也得为utf8,不然在mysqlbinlog 解析出来的sql文件对于中文汉字的会出现乱码,致使最后恢复数据到线上的表中报错。
知足以上条件这样能够极大的保证数据恢复的概率。
固然把控好数据库的权限问题,禁止采用不加where条件的delete 和update语句,以及禁止采用drop,truncate才是从根源保证数据安全行之有效的办法。
前面的几篇博文都有介绍采用第三方的工具binlog-rollback.pl,binlog2sql来还原和恢复数据,其实原理和思路都是一致的。同时在测试使用中发现,这2个工具要求必须是本地数据库服务器安装此2个工具,本地的数据库开启binlog,binlog格式为Row模式而且都是针对本地数据库数据进行恢复的。若是在其余机器上安装binlog-rollback.pl或binlog2sql,而且其把要恢复数据的binlog文件拿到已经安装好binlog-rollback.pl或binlog2sql工具的机器上来恢复数据是会报错的,致使数据恢复失败。mysql
今天我们介绍update执行时忘加where 条件,致使全表更新,采用mysqlbinlog结合sed命令依据binlog日志文件如何来恢复数据。采用此种方式恢复数据对mysql的服务和binlog文件所在的具体服务设备是没有任何限制的,此种方式恢复数据时更灵活sql
查看mysql的binlog格式:
show variables like '%binlog_format%';数据库
查看是否开启了binlog
show variables like '%log_bin%';centos
咱们能够看到log_bin的值为ON,开启状态,OK, 确保了update误操做前,个人数据库 是开启了binog而且binlog格式是row格式,个人数据库数据是能够还原的。
查看log文件:
show master logs;缓存
CREATE TABLE `zx_scores` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `titles` char(15) NOT NULL, `icon` smallint(6) unsigned DEFAULT '0', `integral` int(10) NOT NULL DEFAULT '0', `isdefault` tinyint(1) unsigned NOT NULL DEFAULT '0', `create_time` varchar(20) COLLATE utf8_unicode_ci NOT NULL, `day` date NOT NULL DEFAULT '0000-00-00' COMMENT '日期', PRIMARY KEY (`id`), KEY `integral` (`integral`) ) ENGINE=Innodb AUTO_INCREMENT=0 DEFAULT CHARSET=utf8
给测试表插入数据:安全
insert into zx_scores values(1,'列兵',1,0,1,now(),curdate()); insert into zx_scores values(2,'班长',2,1000,1,now(),curdate()); insert into zx_scores values(3,'少尉',3,2000,1,now(),curdate()); insert into zx_scores values(4,'中尉',4,3000,1,now(),curdate()); insert into zx_scores values(5,'上尉',5,4000,1,now(),curdate()); insert into zx_scores values(6,'少校',6,5000,1,now(),curdate()); insert into zx_scores values(7,'中校',7,6000,1,now(),curdate()); insert into zx_scores values(8,'上校',8,7000,1,now(),curdate()); insert into zx_scores values(9,'少将',9,12000,1,now(),curdate()); insert into zx_scores values(10,'中将',10,17000,1,now(),curdate()); insert into zx_scores values(11,'上将',11,22000,1,now(),curdate()); insert into zx_scores values(12,'大将',12,27000,1,now(),curdate());
select * from test.zx_scores ;服务器
update更新时忘记加where条件限制:
mysql> update zx_scores set titles='班长';
Query OK, 11 rows affected (0.00 sec)
Rows matched: 12 Changed: 11 Warnings: 0
mysql> select * from zx_scores ;
当前的binlog文件为以下:ide
mysql> show master status\G *************************** 1. row *************************** File: mysql-bin.000020 Position: 16042 Binlog_Do_DB: Binlog_Ignore_DB: Executed_Gtid_Set: 1 row in set (0.00 sec)
提示:把此binlog mysql-bin.000020文件移动到其余的机器上也是能够恢复的工具
采用find查找到此文件在服务器上的位置:学习
[root@VM_82_178_centos binlog]# find / -name 'mysql-bin.000020' ; /data/mysql/binlog/mysql-bin.000020
找到这个文件,咱们单独能够把他拷贝到tmp目录下,然返回到mysqllogbin这个文件路径下,再次以前须要确认一下你误操做的大概时间,由于咱们要经过时间范围来搜索日志,执行命令以下:
mysqlbinlog --base64-output=decode-rows -v -v --start-date='2018-10-07 15:25:00' --stop-date='2018-10-07 15:30:00' /tmp/mysql-bin.000020 | grep -C 30 "UPDATE `test`.`zx_scores`"
找到咱们误操做的update 语句,记录下sql上面 # at 开头后面的数字14739(这个标记应该是事务的行号吧),OK,继续执行命令
/usr/local/mysql/bin/mysqlbinlog --no-defaults --base64-output=decode-rows -v -v /tmp/mysql-bin.000020|sed -n '/# at 14739/,/COMMIT/p' >/tmp/update.sql
咱们将这串事务从# at 14739开始到COMMIT之间的行所有提取出来到update.sql里。
到此处,咱们已经拿到了须要还原的sql语句,根据导出的sql语句进行sed命令替换,还原到修改以前sql语句,命令以下:
sed '/WHERE/{:a;N;/SET/!ba;s/\([^\n]*\)\n\(.*\)\n\(.*\)/\3\n\2\n\1/}' update.sql | sed 's/### //g;s/\/\*.*/,/g' | sed /@7/s/,//g | sed '/WHERE/{:a;N;/@7/!ba;s/,/AND/g};s/#.*//g;s/COMMIT,//g' | sed '/^$/d' > rollback.sql
这里sed命令乍一看起来比较复杂,咱们将它分红块来进行分析,由于sed命令是按顺序来执行的,上述命令一共由五条sed命令组成,经过管道分隔,下面咱们来细说一下这些命令都作了什么:
sed '/WHERE/{:a;N;/SET/!ba;s/\([^\n]*\)\n\(.*\)\n\(.*\)/\3\n\2\n\1/}' update.sql
功能:将where 和set位置对调
命令剖析:
/WHERE/ #包含WHERE
:a; #建立一个labela; N; #追加下一个输入行到读取行的末尾,读入到模式空间 /SET/!ba; # 若是不是/SET/,返回a,也就是重复读,一直读到/SET/以前(buffer的内容是WHERE\n.......\nSET)
s/([^\n])\n(.)\n(.*)/\3\n\2\n\1/ 这块能够分三部分来读
第1步: s #替换命令,例如s/a/b 将a替换为b 第2步: \([^\n]*\)\n\(.*\)\n\(.*\) \ #转义字符 [^\n]* == buffer中的where (.*\) #单符号(.)匹配除换行符之外的单个字符,*同上; [^\n]*\ #表明非换行符(回车)开头,*表示匹配零或多个字符 \n #换行 第3步 \3\n\2\n\1 \3 == 内存中的set,第三个括号中的内容 \2 == 内存中原来where与set之间的内容,第二个括号中的内容 \1 == 内存中的where,第一个括号中的内容
[root@VM_82_178_centos tmp]#cp update.sql sed1-update.sql [root@VM_82_178_centos tmp]# sed -i '/WHERE/{:a;N;/SET/!ba;s/\([^\n]*\)\n\(.*\)\n\(.*\)/\3\n\2\n\1/}' sed1-update.sql
sed 's/### //g;s/\/\*.*/,/g'
功能:这句作了两个事情1.把字符串### 替换成 空格 2.把/*日后的内容 替换成,
s/### //g #将### 替换成空串, \ #转义字符 \/\*.* #匹配/*以后出换行符外全部内容
[root@VM_82_178_centos tmp]#cp sed1-update.sql sed2-update.sql [root@VM_82_178_centos tmp]# sed -i 's/### //g;s/\/\*.*/,/g' sed2-update.sql
内容以下:
[root@VM_82_178_centos tmp]# grep -C 8 'UPDATE `test`.`zx_scores`' sed2-update.sql ............. [root@VM_82_178_centos tmp]# grep -C 8 'UPDATE `test`.`zx_scores`' sed2-update.sql|tail -26 UPDATE `test`.`zx_scores` SET @1=11 , @2='上将' , @3=11 , @4=22000 , @5=1 , @6='2018-10-07 15:20:30' , @7='2018:10:07' , WHERE @1=11 , @2='班长' , @3=11 , @4=22000 , @5=1 , @6='2018-10-07 15:20:30' , @7='2018:10:07' , UPDATE `test`.`zx_scores` SET @1=12 , @2='大将' , @3=12 , @4=27000 , @5=1 , @6='2018-10-07 15:20:37' , @7='2018:10:07' , [root@VM_82_178_centos tmp]#
sed /@7/s/,//g
功能:这句把字符串包含@7的行中的所有(,)换成空格
/@7/ #匹配包含@7的行 s/,// #将,替换为空串 g #所有替换
[root@VM_82_178_centos tmp]#cp sed2-update.sql sed3-update.sql [root@VM_82_178_centos tmp]# sed -i /@7/s/,//g sed3-update.sql
替换前文件内容:
[root@VM_82_178_centos tmp]# grep '@7' sed2-update.sql |tail -5 @7='2018:10:07' , @7='2018:10:07' , @7='2018:10:07' , @7='2018:10:07' , @7='2018:10:07' ,
替换后的文件内容:
[root@VM_82_178_centos tmp]# grep '@7' sed3-update.sql |tail -5 @7='2018:10:07' @7='2018:10:07' @7='2018:10:07' @7='2018:10:07' @7='2018:10:07'
sed '/WHERE/{:a;N;/@7/!ba;s/,/AND/g};s/#.*//g;s/COMMIT,//g'
功能:这句作了三件事 1.就是把WHERE 至@7之间的全部逗号,替换成AND 2.#.* 就是把#在的行替换为空格 3.就是把匹配到的COMMIT, 替换为空格
/WHERE/{:a;N;/@7/!ba;s/,/AND/g} #将WHERE至@7之间的行尾的(,)替换为(AND) s/#.*//g #将#号开头的整行字符替换为空串。 s/COMMIT,//g #将(COMMIT,)替换为空行;
[root@VM_82_178_centos tmp]#cp sed3-update.sql sed4-update.sql [root@VM_82_178_centos tmp]# sed -i '/WHERE/{:a;N;/@7/!ba;s/,/AND/g};s/#.*//g;s/COMMIT,//g' sed4-update.sql
替换后的文件的内容:
[root@VM_82_178_centos tmp]# tail -20 sed4-update.sql UPDATE `test`.`zx_scores` SET @1=12 , @2='大将' , @3=12 , @4=27000 , @5=1 , @6='2018-10-07 15:20:37' , @7='2018:10:07' WHERE @1=12 AND @2='班长' AND @3=12 AND @4=27000 AND @5=1 AND @6='2018-10-07 15:20:37' AND @7='2018:10:07'
sed '/^$/d' > sed5-update.sql /^$/ #查找缓存内容中全部的空行 d #删除 > sed5-update.sql #输出缓存中的内容到sed5-update.sql
[root@VM_82_178_centos tmp]#cp sed4-update.sql sed5-update.sql [root@VM_82_178_centos tmp]# sed -i '/^$/d' sed5-update.sql
提示:对于第3个sed语句中的@7和第四个sed语句中@7能够替换成你当前表zx_score的最大列数@max。
sed -i -r '/WHERE/{:a;N;/@7/!ba;s/(@7=.*)/\1\;/g}' sed6-update.sql
这句是在where语句后@7最后一个字段加(;),若是后执行这句请将@7换成对应的列名便可。
[root@VM_82_178_centos tmp]#cp sed5-update.sql sed6-update.sql sed -i -r '/WHERE/{:a;N;/@7/!ba;s/(@7=.*)/\1\;/g}' sed6-update.sql ###此处采用的是后执行此命令
将sed6-update.sql中的@1,@2,@3,@4,@5,@6,@7替换成对应的列名
sed -i 's/@1/列1/g;s/@2/列2/g;s/@3/列3/g;s/@4/列4/g;s/@5/列5/g;s/@6/列6/g;s/@7/列7/g' sed6-update.sql
此处采用的是先把@1,@2.....@6,@7替换为对应的zx_scores表的列名。
[root@VM_82_178_centos tmp]# sed -i 's/@1/id/g;s/@2/titles/g;s/@3/icon/g;s/@4/integral/g;s/@5/isdefault/g;s/@6/create_time/g;s/@7/day/g' sed6-update.sql
换换后的内容以下:
[root@VM_82_178_centos tmp]# tail -17 sed6-update.sql UPDATE `test`.`zx_scores` SET id=12 , titles='大将' , icon=12 , integral=27000 , isdefault=1 , create_time='2018-10-07 15:20:37' , day='2018:10:07' WHERE id=12 AND titles='班长' AND icon=12 AND integral=27000 AND isdefault=1 AND create_time='2018-10-07 15:20:37' AND day='2018:10:07'
而后将@7转换为对应的列名day
sed -i -r '/WHERE/{:a;N;/@7/!ba;s/(@7=.*)/\1\;/g}' sed6-update.sql [root@VM_82_178_centos tmp]# sed -i -r '/WHERE/{:a;N;/day/!ba;s/(day=.*)/\1\;/g}' sed6-update.sql
转换后的内容以下:
[root@VM_82_178_centos tmp]# tail -17 sed6-update.sql UPDATE `test`.`zx_scores` SET id=12 , titles='大将' , icon=12 , integral=27000 , isdefault=1 , create_time='2018-10-07 15:20:37' , day='2018:10:07' WHERE id=12 AND titles='班长' AND icon=12 AND integral=27000 AND isdefault=1 AND create_time='2018-10-07 15:20:37' AND day='2018:10:07' ;
到此处数据还原已经完成,直接把sed6-update.sql 数据导入到mysql中便可
至此,咱们的sql语句已经成功还原,美中不足的一条sql语句写成不少行,看起来不顺眼,来咱们再继续优化下,执行语句:
[root@VM_82_178_centos tmp]# cat sed6-update.sql | tr "\n" " " > rollback.sql 将全部的换行替换成空格,此处用tr命令,因个人数据量比较大,tr执行效率相对较高,也能够用sed命令sed -i ':label;N;s/\n/ /;b label' rollback.sql,效果都是同样的
sed -i 's/\;/ LIMIT 1\;\n/g' rollback.sql
在每个;前面加上 LIMIT 1,后面加上换行符:
[root@VM_82_178_centos tmp]# sed -i 's/\;/ LIMIT 1\;\n/g' rollback.sql
恢复到MySQL
还原成功:到此处mysqlbinlog结合sed命令恢复数据库数据介绍完毕,欢迎留言一块儿探讨交流学习