SQL更新操作(update/delete)预防措施及案例·第三话

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/weixin_42018518/article/details/88899187

目录

一、写在最前:

1、update

2、delete

3、总结如下表:key表示索引、const表示常量 

二、用binlog恢复数据(举个栗子)

三、小结


一、写在最前:

        实际工作中,有时会直接在数据库中操作数据,比如对数据进行delete或者update操作,当进行这些操作的时候,如果没有加上where条件或者where条件不合理,那么导致的结果可想而知,如果操作的又是线上数据库,那么这个后果将会非常严重。当事情发生后,我们要想办法补救,针对于MySQL数据库,有个很出名的工具Log Exploer,具体操作使用大家可以自行搜索,但是大多是需要付费的,而且还无法灵活运用的各种生产场景...我们用MySQL的事务日志(binlog)来恢复这些受影响的数据。希望能进一步做到最快的危机处理

        当然,良好的SQL习惯才是最好的预防方案,我们会制定越来越严谨的规范帮助你并且约束我们哦。

        首先我们针对主库做如下配置:

        mysql> show variables like 'sql_safe_updates';

        master:set global sql_safe_updates=on;

       在主库设置以上这个参数时需要注意以下几点:

        a、设置前需要确认程序中所有的update和delete都符合sql_safe_updates的限制规范,不然程序会报错,具体规范如下面的update与delete。

        b、5.6+是global&session级别;低版本的数据库只能在程序创建session时设置带上set sql_safe_updates=on;高版本的数据库可以直接set global  sql_safe_updates=on,设置完成后让程序重连后生效。

         其次,设置从库为只读:

        mysql> show variables like '%read_only%';

        slave:set global read_only=1;

1、update

a、报错条件:不带where、带where无索引、where条件为常量

     不带where:update  table_name  set  ctime=now();

     带where无索引:update  table_name  set  ctime=now()  where  test_type='test';

     where条件为常量:update  table_name  set  ctime=now()  where 1;

 b、执行条件:带where带索引、不带where+带limit、带where无索引+limit、带where有索引+limit、where条件为常量+limit

       带where带索引:update  table_name  set  ctime=now()  where id=2;

       不带where+带limit: update  table_name  set  ctime=now()  limit  1;

       带where无索引+limit:update  table_name  set  ctime=now()  where  test_type='test'  limit 1;

       带where有索引+limit:update  table_name  set  ctime=now()  where id =2  limit 1;

       where条件为常量+limit:update  table_name  set  ctime=now()  where 1 limit 1;

 2、delete

 相对于update,delelte的限制会更为严格;where条件为常量或者为空,将不予执行。

a、报错条件:不带where、带where无索引、不带where+带limit、where条件为常量、where条件为常量+limit

     不带where:delete  from table_name;

     带where无索引:delete  from  table_name  where  test_type='test';

     不带where+带limit: delete  from  table_name  limit 1;

     where条件为常量:delete  from  table_name  where 1;

     where条件为常量+limit:delete from  table_name  where 1 limit 1;

b、执行条件:带where带索引、带where无索引+limit、带where有索引+limt

      带where带索引:delete from  table_name  where  id=2;

      带where无索引+limit:delete  from  table_name  where  test_type='test'  limit 1;

      带where有索引+limit:delete  from  table_name  where id =2  limit1;

3、总结如下表:key表示索引、const表示常量 

操作 

no where

where key

where nokey 

limit

where nokey+limit

where key+limit     

where const

where const+limit

delete    

NO

YES

NO

NO

YES

YES

NO    

NO

update

NO

YES

NO

YES

YES

YES

NO

YES

二、用binlog恢复数据(举个栗子)

    众所周知,binlog日志对于mysql数据库来说是十分重要的。在数据丢失的紧急情况下,我们往往会想到用binlog日志功能进行数据恢复(定时全备份+binlog日志恢复增量数据部分),化险为夷!

    初步了解binlog,MySQL的二进制日志binlog可以说是MySQL最重要的日志,它记录了所有的DDL和DML语句(除了数据查询语句select),以事件形式记录,还包含语句所执行的消耗的时间,MySQL的二进制日志是事务安全型的。

    一般来说开启binlog日志大概会有1%的性能损耗。

    这种时候,一定不要慌张!!!先仔细查看最后一个binlog日志,并记录下关键的pos点,到底是哪个pos点的操作导致了数据库的破坏(通常在最后几步)。

1、先备份一下最后一个binlog日志文件

2、接着执行一次刷新日志索引操作,重新开始新的binlog日志记录文件。

    举个栗子是mysql-bin.000003

    这个文件不会再有后续写入了,因为便于我们分析原因及查找ops节点,以后所有数据库操作都会写入到下一个日志文件。

    mysql> flush logs;

    mysql> show master status;

3、读取binlog日志,分析问题。

    读取binlog日志的方法可以参考[SQL操作问题及解决案例·第二话]。

    方法一:使用mysqlbinlog读取binlog日志:

    # mysqlbinlog mysql-bin.000003

    方法二:登录服务器,并查看(推荐此种方法)

    mysql> show binlog events in 'mysql-bin.000003';

    或者:

    mysql> show binlog events in 'mysql-bin.000003'\G;

4、如果是整个库问题,先把最新全备份的数据恢复

4.1 找到该数据库最新全备位置:

    通过xx.xx.1.49的备份信息库查看所需恢复库的相关信息。

备份状态监控提醒

bash /home/wufei/monitor/bin/backup_monitor_wf.sh -d [yyyy-mm-dd]:-d指定日期(有日期校验功能哦,仅限于合法date,当然未来的合法date也是不行的,备份还是有保存天数的,超过了保质期也是查询不到的),然后直接在窗口打印改天备份信息,如果该天的详细信息已经存在,会直接读取缓存日志,不会再去麻烦数据库。

4.2 找到所需备份,在当前服务器上进行还原:

    # innobackupex --apply-log --redo-only [备份文件(绝对路径)]

4.3 将还原的文件复制到目标库

    # rsync -avprP -e ssh [备份文件(绝对路径)] IP:[目标库data路径(绝对路径)]/data/

    # chown -R mysql.mysql data

    # 重启该MySQL实例

4.4 查看xtrabackup_slave_info文件,提取其中的master_log_file和master_log_pos信息,然后在目标库上进行change master to操作:

    # cat data/xtrabackup_slave_info

CHANGE MASTER TO MASTER_LOG_FILE='log_bin.00000*', MASTER_LOG_POS=number

    mysql> CHANGE MASTER TO MASTER_HOST='master_ip',MASTER_USER='username',MASTER_PASSWORD='password',MASTER_LOG_FILE='log_bin.00000*',MASTER_LOG_POS=number;

确认全备时间,但是这仅仅只是恢复了全备之前的数据,全备之后的数据还没有恢复回来!!

怎么办呢?

莫慌!这可以根据全备时间点之后的binlog日志进行恢复。

5、从binlog日志恢复数据

  5.1 恢复命令的语法格式:

mysqlbinlog mysql-bin.0000xx | mysql -u用户名 -p密码 数据库名

  5.2 常用参数选项解释:

--start-position=875 起始pos点

--stop-position=954 结束pos点

--start-datetime="2019-9-25 22:01:08" 起始时间点

--stop-datetime="2019-9-25 22:09:46" 结束时间点

--database=zyyshop 指定只恢复zyyshop数据库(一台主机上往往有多个数据库,只限本地log日志)

  5.3 不常用选项:  

-u --user=name 连接到远程主机的用户名

-p --password[=name] 连接到远程主机的密码

-h --host=name 从远程主机上获取binlog日志

--read-from-remote-server 从某个MySQL服务器上读取binlog日志

6、如果只是单表问题

  6.1 先备份一下最后一个binlog日志文件

  6.2 这个时候我们就不需要还原全备到问题库了,只需要还原全部到一个空闲MySQL实例,步骤如第4步。

然后mysqldump(以下拿最新误操作事件做记录)

开启GTID情况:mysqldump -uroot -pxxxxx -h127.0.0.1 -P3215 --set-gtid-purged=OFF xxxx_account t_pandora_account > /tmp/t_pandora_account_wf.sql

未开启GTID情况:mysqldump -uroot -pxxxxx -h127.0.0.1 -P3215  xxxx_account t_pandora_account > /tmp/t_pandora_account_wf.sql

    让后修改/tmp/t_pandora_account_wf.sql文件并全局替换t_pandora_account为t_pandora_account_wf:【:%s/t_pandora_account/t_pandora_account_wf/g】

 6.3 到发送误操作的库,建立临时表t_pandora_account_wf,并导入/tmp/t_pandora_account_wf.sql数据

Mysql> create table t_pandora_account_wf like t_pandora_account;

 6.4 更新误操作表对应字段数据,把正确数据t_pandora_account_wf表中对应字段数据更新到t_pandora_account中:

Mysql>update t_pandora_account,t_pandora_account_wf set t_pandora_account.account_mobile=t_pandora_account_wf.account_mobile where t_pandora_account.account_id=t_pandora_account_wf.account_id;

    这样全备以前的数据就全备恢复了,接下来就是从binlog中查找出全备以后的数据并更新到t_pandora_account中。

 6.5 筛选备份出来的binlog(删除binlog中注释等无用数据)

   先删除全备时间点以前的行,然后:

   # sed -r '/^#/d' mysql-bin.001966.log > mysql-bin.001966.log01

   # sed -r '/^SET/d' mysql-bin.001966.log01 > mysql-bin.001966.log02

   # sed -r '/^BEGIN/d' mysql-bin.001966.log02 > mysql-bin.001966.log03

   # sed -r '/^COMMI/d' mysql-bin.001966.log03 > mysql-bin.001966.log04

   # sed -r '/^DELIMITER/d' mysql-bin.001966.log04 > mysql-bin.001966.log05

   # sed -r '/^ROLLBACK/d' mysql-bin.001966.log05 > mysql-bin.001966.log06

   # grep "t_pandora_account " mysql-bin.001966.log06 > a.log

   # grep "INSERT" a.log > i.log

   # grep "UPDA" a.log > u.log

   # sed 's/$/&;/' u.log> update.log

   # sed 's/$/&;/' i.log > insert.log

   还有记得把update.log与insert.log中的表名替换了(:%s/t_pandora_account/t_pandora_account_wf/g)并上传到/tmp/路径下。

   然后truncate table t_pandora_account_wf;临时表中数据,source/tmp/insert.log与source /tmp/update.log

   最后按照之前方式把全备时间点之后的数据更新就去就搞定了(备份binlog之后的数据时不受影响的)。。。。。

三、小结

实际是将读出的binlog日志内容,通过管道符传递给mysql命令。这些命令、文件尽量写成绝对路径。所谓恢复,就是让mysql将保存在binlog日志中指定段落区间的sql语句逐个重新执行一次而已。

a)完全恢复(需要手动vim编辑binlog,将那条drop、update、delete语句剔除掉)

b)指定pos结束点恢复(部分恢复,建议),参考[SQL操作问题及解决案例·第二话]。

c)除了用pos节点的办法进行恢复,也可以通过指定时间节点区间进行恢复,按时间恢复需要用mysqlbinlog命令读取binlog日志内容,找时间节点(部分恢复,一般不建议)。

猜你喜欢

转载自blog.csdn.net/weixin_42018518/article/details/88899187
今日推荐