数据库 7年前

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

作者头像 刘宇帅
3042 0

事情起因

有个同事需要一个管理系统,需要管理一些图片什么的,我就想着让他用我写的这套系统,然后就想着把代码和库复制一份出来给他用就可以了,然后我顺便可以把一些需要写到配置里的东西提炼下,让这套系统成为一个可复制部署的 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进制数据到日志文件
作者头像

刘宇帅

非著名程序员,全栈开发工程师,长期专注系统开发与架构设计。

提示

功能待开通!


暂无评论~

相关文章

杀掉 MySQL 慢查询

show processlist show processlist 用来查看 MySQL 正在运行的线程,如果使用 root 账号可以看到所有用户的线程或者只能看到所使用账号自己的线程,当然如果该用户被赋予了 Process 权限也是可以查看所有用户的线程的。 命令使用展示: mysql&gt; show processlist; +-----+------+-----------+------+---------+------+----------+------------------+ | Id | User | Host | db | Command | Time | S

常见数据库分类

常见的数据库主要分为三类,SQL(关系型数据库)、NoSQL(非关系型数据库)、NewSQL。 SQL SQL即关系型数据库(RDBMS — Relational Database Management System),SQL 是基于关系型数据模型设计的数据库,SQL用二维表格表示实体以及实体之间的关系,对数据的操作也几乎全部建立在一个或多个关系表格上,通过对这些关联的表格分组、合并、连接、选取等运算来实现数据库的管理。 常见的关系型数据库有: Orcale MySQL Microsoft SQL server PostgreSQL SQLLite MariaDB 关系型数据库特点 事务性

MySQL 字符串比较不区分大小写

最近做了一个简单的短链接服务,短链接使用自增 id 转化为 62 进制(10 个数字+26个小写字母+26个大写字母),但是发现插入数据库报了索引插入重复。 展示 创建测试表 CREATE TABLE `test` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `short_url` varchar(255) NOT NULL COMMENT '测试', PRIMARY KEY (`id`), UNIQUE KEY `short_url` (`short_url`) ) ENGINE=InnoDB AUTO_INCREMENT

SQL 查询如何按 in 后面的数组列表的顺序返回数据

今天要查询一个根据手机号返回用户的姓名,但是想要的 SQL 查询的结果的顺序和 in 后面数组里手机号顺序一样,使用 order by field(phone,111,222) select name,phone from users where phone in (18800000000,18800000000001) order by field(phone, 18800000000,18800000000001)

MySQL修改表的存储引擎

MySQL修改表的存储引擎有三种方式 alter table 该方式需要执行很长时间 mysql&gt; alter table mytable engine=InnoDB; 导入导出 使用mysqldump导出表,修改导出文件里的create table后面表的名称。 创建新的表。 mysqldump导出的表里会包含drop table,所以要注意!删除该句。防止数据丢失 创建和查询 mysql&gt; create table new_table_name like table_name; mysql&gt; alter table new_table_name engin