1 linux操作系统删除文件的原理
在使用rm删除文件时,Linux操作系统并不对这个文件的inode和数据块做清除操作,而是从父目录的块里删除这个文件的名字,这个文件的名字是指向文件的inode的。
如果一个文件有硬链接,执行rm命令,就是删除一个硬链接,文件本身还在。当文件打开时,打开文件的进程会维护一个文件描述符表,包含此文件的文件描述符,这个文件描述符通过系统级的打开文件表,指向系统级的inode表里的inode条目,这个inode就是打开文件的inode的内存拷贝。通过文件描述符,同样可以操作文件,利用这个特性,在打开文件被误删除时,可以利用进程的文件描述符找到被误删除的文件,拷贝文件至原目录就可以恢复了。下面的图片来自网络,可以看的更明白点:
2 MySQL数据库运行时被误删数据文件的处理
2.1 处理思路
明白了linux操作系统下删除文件的原理,对MySQL正在运行时数据文件被误删除应该怎样处理也就有了大概的思路。
在这个时候,要注意的是不能关闭数据库,如果数据库关闭了,文件描述符也就没了,对文件的引用没了,这时要恢复文件就得想其它的办法了。数据库不能关闭,连接数据库的应用则必须要关闭,否则,我们拷贝的数据文件可能不是最新的,也可能会有不一致的数据。
关闭应用后,就可以利用文件描述符来拷贝恢复被误删的文件了。
2.2 故障模拟
为了使故障模拟简单直观一点,使用独立的表空间创建一张表,表的名字是t,表的数据文件是t.ibd,在数据库运行时,将此文件删除。
rm t.ibd
2.3 处理步骤
查找mysqld进程的进程号
要想找到被删除文件的文件描述符,首先要找到打开文件的进程,myql数据库时单进程多线程模型,打开文件的进程即是MySQL数据库的后台进程mysqld
ps -ef|grep mysqld
这里查到的mysqld的进程id是563,进入进程目录,进程目录fd目录下是进程打开的文件
cd /proc/563
ls
cd fd
进入目录显示进程打开的文件
ls -l
lr-x------ 1 root root 64 Jul 23 09:33 0 -> /dev/null
l-wx------ 1 root root 64 Jul 23 09:33 1 -> /usr/local/mysql-5.7.34/data/DESKTOP-FVJ8TG1.err
lrwx------ 1 root root 64 Jul 23 09:33 10 -> /usr/local/mysql-5.7.34/data/ibtmp1
lrwx------ 1 root root 64 Jul 23 09:33 11 -> '/tmp/ibdI0AsK (deleted)'
lrwx------ 1 root root 64 Jul 23 09:33 12 -> /usr/local/mysql-5.7.34/data/mysql/servers.ibd
lrwx------ 1 root root 64 Jul 23 09:33 27 -> /usr/local/mysql-5.7.34/data/test/t1.ibd
lrwx------ 1 root root 64 Jul 23 09:33 28 -> '/usr/local/mysql-5.7.34/data/test/t.ibd (deleted)'
最后一行是被删除的t.ibd文件,它的文件句柄是28,通过文件句柄可以拷贝文件,将
拷回原目录
cp 28 /usr/local/mysql-5.7.34/data/test/t.ibd
登录数据库检查数据
mysql> use test;
Database changed
mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)
2.4 后续处理
数据库重启后报下面错误
mysql> select count(*) from t;
ERROR 1812 (HY000): Tablespace is missing for table `test`.`t`.
表空间找不到了,这时候表的定义在数据库还在,表的数据文件也在,只要重新导入表空间就可以了。导入表空间前先检查一下文件的属主和权限,由于是用root用户做的拷贝,文件的属主也是root,需要改为mysql,进入操作系统更改文件属主为MySQL用户
chown mysql:mysql t.ibd
重新导入表空间
alter table t import tablespace;
检查数据
mysql> select count(*) from t;
+----------+
| count(*) |
+----------+
| 1000 |
+----------+
1 row in set (0.00 sec)
可以查询到数据了。