我是如何成功删库并手动进行数据恢复的

数据库 刘宇帅 5年前 阅读量: 1407

事情起因

有个同事需要一个管理系统,需要管理一些图片什么的,我就想着让他用我写的这套系统,然后就想着把代码和库复制一份出来给他用就可以了,然后我顺便可以把一些需要写到配置里的东西提炼下,让这套系统成为一个可复制部署的 CMS 系统。

实操删库过程

第一步

很熟练的把代码复制一份,nginx 配置复制并修改一份。

第二步:删库

  1. 登陆数据库
  2. 新建库、切到新建库
  3. 删库:因为我部署这个博客系统的时候有把初始化数据库的 sql 文件放到服务器上,我看了下还在。然后直接source init.sql。

3步完成之后,有种莫名的感觉涌上心头,然后打开我的博客,发现里面数据已经空了。。其实我这里使用了Phalcon框架自带的 migrate 功能,但由于 Phalcon 这个功能做的并不好用,并且还需要下载一个工具库,所以第一次部署的时候就直接导出 sql 来搞了。

为什么会删库

我系统上的 init.sql 是用 mysqldump 从开发机器上导出的,我一直以为里面只包含建表数据,所以才有了上面的很溜的操作,这一出事很显然就是”我以为“错了,然后查看 init.sql 文件有以下代码。

CREATE DATABASE /*!32312 IF NOT EXISTS*/ `lfuture` /*!40100 DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci */;

USE `lfuture`;

--
-- Table structure for table `app_activity_info`
--

DROP TABLE IF EXISTS `app_activity_info`;

欲哭无泪。
mysqldump 导出数据的几种方式。

  1. mysqldump -uroot -p -d dbname >~/Downloads/dbname.sql 只有表结构
  2. mysqldump -uroot -p dbname >~/Downloads/dbname.sql 表结构及数据
  3. mysqldump -uroot -p --databases dbname >~/Downloads/dbname.sql 库的创建、切换及表结构和表数据

而我用的就是第三种,这种方式导出的 sql 会去尝试建库切库、删表、新建表、导入数据。

数据恢复

我这个博客刚上线两周,还没有做数据库备份、也没开启binlog,不过幸亏我在 nginx log 里添加了 request_body,而我的博客里目前只有几博客、几条评论,并且博客刚上线两周,我配置的 crontab 去删除30天以前的 nginx 日志也还没起作用,所以说使用日志来恢复是完全没问题了。。

解析nginx日志

其中一条日志例子(有些敏感信息我替换成了secret)

remote_addr=[218.30.116.3] http_x_forward=[-] time=[20/Jun/2018:10:45:00 +0800] request=[POST /adminApiGate HTTP/1.1] status=[200] byte=[514] elapsed=[0.015] refer=[secrete~~] body=[{\x22module\x22:\x22Dashboard\x22,\x22handler\x22:\x22Statistics\x22,\x22method\x22:\x22statistics\x22}] ua=[Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36] cookie=[secret] gzip=[-] x_from=[-] msec=[1529462700.279] http_host=[secret] http_accept=[application/json|gzip, deflate, br|zh-CN,zh;q=0.9,en;q=0.8] upstream_response_time=[0.015] sent_http_set_cookie=[-]

用 grep、awk 找出所有的 adminApiGate 的日志的request_body字段(adminApiGate 是后台 api 接口地址)

grep 'adminApiGate' lfutur* |awk -F 'body\=\\[' '{print $2}'|awk -F '] ua' '{print $1}' >/home/work/recovery.data

nginx 默认会把 request_body 转换成16进制,所以直接当成 json 是无法处理的,需要做一步转换,这里使用 PHP 来做,代码如下.

$file = "/Users/liushuai/Downloads/recovery.data";
$saveFile = "/Users/liushuai/Downloads/result.csv";
$fp = fopen($file, 'r');
$csvFp = fopen($saveFile, 'w');
fputcsv($csvFp, array('module', 'handler', 'method', 'payload'));
while ($line =fgets($fp)) {
    $pt = 0;
    $str = '';
    while ($pt < strlen($line)) {
        if ($line[$pt] == '\\' && $line[$pt + 1] == 'x') {
            $str .= $this->hexToStr($line[$pt + 2]. $line[$pt + 3]);
            $pt += 4;
        } else {
            $str .= $line[$pt];
            $pt += 1;
        }
    }
    $result = json_decode($str, true);
    $payload = $result['payload']?? [];
    fputcsv($csvFp, array($result['module'], $result['handler'], $result['method'], var_export($payload, true)));
}

function hexToStr($hex){
    $str="";
    for($i=0;$i<strlen($hex)-1;$i+=2)
        $str.=chr(hexdec($hex[$i].$hex[$i+1]));
    return  $str;
}

数据恢复

因为我需要的只是博客、标签的信息,我如果可以拿到 module handler method 字段过滤下就可以,所以我直接把解析结果放到了 csv 文件里,然后筛选,手动入库。
这里有思考如果数据量比较大,可以在日志里取出请求时间,然后对一个资源的 add update接口做排序取最后一条即可,然后直接程序写库。

TODO

  1. 数据库定时备份
  2. 开启binlog
  3. 完善 migrate,避免手动操作
  4. 可以考虑去修改 nginx 配置不要打印16进制数据到日志文件

提示

功能待开通!


暂无评论~