一. Linux 下文件删除过程分析
1. 简介 i_count 和 i_nlink
Linux 的文件系统是通过 link 的数量来控制文件删除的,一般来说,每个文件都有 2 个 link 计数器 icount 和 inlink。
- i_count 表示当前文件使用者,或者调用者的数量。
- i_nlink 表示硬链接的数量
二. i_count 和 i_nlink 与文件的对应关系

如上图所示
文件被一个进程调用,icount 就加 1 。 如上图,一个文件被三个进程调用,则 icount=3。
三. 如何查看 i_nlink 数量
i_nlink 为硬链接的数量,如下图: 211452463 为该文件 inode 号,1 为该文件硬链接数。
[oracle@host01 pfile]$ ls -il
total 4
211452463 -rw-r-----. 1 oracle oinstall 1944 Apr 19 2023 init.ora.3192023232045
复制
此时为该文件创建一个硬链接。
ln init.ora.3192023232045 init.ora.bk
[oracle@host01 pfile]$ ls -il
total 8
211452463 -rw-r-----. 2 oracle oinstall 1944 Apr 19 2023 init.ora.3192023232045
211452463 -rw-r-----. 2 oracle oinstall 1944 Apr 19 2023 init.ora.bk
复制
可以看到硬链接数量有所变化。

根据硬链接的概念,这两个文件其实是一个文件,更改一个文件后,另一个文件也会跟着更改。
四. 如何查看 i_count 数量
[oracle@host01 PDBPROD1]$ lsof | grep example01.dbf
ora_dbw0_ 3689 oracle 283uW REG 252,0 312877056 8697085 /u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
ora_p000_ 3736 oracle 270u REG 252,0 312877056 8697085 u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
ora_p002_ 3760 oracle 271u REG 252,0 312877056 8697085 /u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
ora_p003_ 3762 oracle 263u REG 252,0 312877056 8697085 u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
ora_m004_ 3997 oracle 273u REG 252,0 312877056 8697085 /u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
复制

两种方式均可以清楚看到 i_count 为 5。
五. 什么文件删除能恢复,什么文件不能恢复?
了解了 icount 和 inlink 的查看方法后,就可以知道什么时候文件能恢复,什么时候不能恢复。
i_nlink 及 i_count 任意一个不为 0,文件可以直接恢复。
i_nlink 及 i_count 都为 0 时,文件才会真正的被删除,但也能完全恢复。
下图展示了操作系统文件什么时候可以找回,什么时候无法找回。

一条 rm 命令下来的时候,操作系统做了这些事。
- 查找文件对应的 inode
- 将 inode 表中指向 A 文件的数据块指针删除
- 在 inode 位图块中将 A 文件 inode 标记未使用
- 在块位图块中将 A 文件占用的块标记未使用
如果此时正好一个新文件过来占用了 A 的块。
六. 模拟 MOP 数据库删库,实际恢复一下
MYSQL: 删除 MYSQL 共享表空间文件以及日志文件
注意:删除后 i_nlink 为 0,但数据库没有停库,i_count 不为 0。根据规则所以可以直接恢复。
[root@mysql data]# rm -rf ibdata1 ib_logfile0 ib_logfile1
复制
此时进入数据库,数据库仍可以使用
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| DEPT |
| EMP |
| Tab_A |
| salgrade |
+----------------+
4 rows in set (0.00 sec)
mysql> create table DELETETEST (id int,name varchar(20));
Query OK, 0 rows affected (0.01 sec)
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| DELETETEST |
| DEPT |
| EMP |
| Tab_A |
| salgrade |
+----------------+
5 rows in set (0.00 sec)
复制
此时万万不要停库,一停库,文件不被引用了,icount 和 inlink 同时为 0,文件可能无法恢复了。
根据进程号查看内存文件系统 3665
[root@mysql ~]# ps -ef| grep mysqld
root 3413 1 0 04:53 ? 00:00:00 /bin/sh /usr/local/mysql/bin/mysqld_safe --datadir=/usr/local/mysql/data --pid-file=/usr/local/mysql/data/mysql.pid
mysql 3665 3413 0 04:53 ? 00:00:03 /usr/local/mysql/bin/mysqld --basedir=/usr/local/mysql --datadir=/usr/local/mysql/data --plugin-dir=/usr/local/mysql/lib/plugin --user=mysql --log-error=/usr/local/mysql/data/mysql.err --pid-file=/usr/local/mysql/data/mysql.pid --socket=/tmp/mysql.sock --port=3306
root 4179 3307 0 05:27 pts/1 00:00:00 grep --color=auto mysqld
复制
proc 目录恢复删除文件
lrwx------. 1 root root 64 Jan 10 04:54 3 -> /usr/local/mysql/data/ib_logfile0 (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 30 -> socket:[51799]
lrwx------. 1 root root 64 Jan 10 04:54 31 -> socket:[53291]
lrwx------. 1 root root 64 Jan 10 04:54 32 -> usr/local/mysql/data/mysql/time_zone_name.ibd
lrwx------. 1 root root 64 Jan 10 04:54 33 -> usr/local/mysql/data/mysql/time_zone.ibd
lrwx------. 1 root root 64 Jan 10 04:54 34 -> usr/local/mysql/data/mysql/time_zone_transition.ibd
lrwx------. 1 root root 64 Jan 10 05:31 36 -> usr/local/mysql/data/test/DELETETEST.ibd
lrwx------. 1 root root 64 Jan 10 04:54 4 -> tmp/ib56RtkD (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 5 -> tmp/ibuMillJ (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 6 -> tmp/ib99IdmP (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 7 -> tmp/ibTgVTC1 (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 8 -> usr/local/mysql/data/ib_logfile1 (deleted)
lrwx------. 1 root root 64 Jan 10 04:54 9 -> /usr/local/mysql/data/ibdata1 (deleted)
复制
把文件拷贝出来进行恢复
[root@mysql ~]# cp /proc/3665/fd/8 /usr/local/mysql/data/ib_logfile1
[root@mysql ~]# cp /proc/3665/fd/9 /usr/local/mysql/data/ibdata1
[root@mysql ~]# cp /proc/3665/fd/3 /usr/local/mysql/data/ib_logfile0
复制
恢复之后要更改权限
[root@mysql data]# chown -R mysql:mysql ./*
复制
重启 MYSQL 后 验证数据库完整性
[root@mysql data]# systemctl restart mysql
[root@mysql data]# mysql
Welcome to the MySQL monitor. Commands end with ; or \g.
Your MySQL connection id is 2
Server version: 5.7.36 MySQL Community Server (GPL)
Copyright (c) 2000, 2021, Oracle and/or its affiliates.
Oracle is a registered trademark of Oracle Corporation and/or its
affiliates. Other names may be trademarks of their respective
owners.
Type 'help;' or '\h' for help. Type '\c' to clear the current input statement.
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| mysql_to_oceanbase |
| obmysql |
| ogg |
| performance_schema |
| sys |
| test |
+--------------------+
8 rows in set (0.01 sec)
mysql> use test
Reading table information for completion of table and column names
You can turn off this feature to get a quicker startup with -A
Database changed
mysql> show tables;
+----------------+
| Tables_in_test |
+----------------+
| DELETETEST |
| DEPT |
| EMP |
| Tab_A |
| salgrade |
+----------------+
5 rows in set (0.00 sec)
mysql>
复制
Oracle: 删除数据文件 example
[oracle@host01 PDBPROD1]$ rm -rf example01.dbf
复制
在 Oracle 上创建该表空间上的表已经不行了。
SQL> create table TESTDELETE (id number,name varchar(20)) default tablespace EXAMPLE;
create table TESTDELETE (id number,name varchar(20)) default tablespace EXAMPLE
*
ERROR at line 1:
ORA-00901: invalid CREATE command
SQL> create table TESTDELETE (id number,name varchar(20)) tablespace EXAMPLE;
create table TESTDELETE (id number,name varchar(20)) tablespace EXAMPLE
*
ERROR at line 1:
ORA-01116: error in opening database file 18
ORA-01110: data file 18:
'/u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf'
ORA-27041: unable to open file
Linux-x86_64 Error: 2: No such file or directory
Additional information: 3
复制
相比 Mysql,Oracle 是多进程,那么该怎么找是被哪个进程引用了?稍微了解一下 Oracle 的内存结构,就能知道 DBWR 进程负责数据文件的读写,找到 DBWR 进程号,去 PROC 目录下恢复数据文件:
[oracle@host01 fd]$ cd /proc/3689/fd/
复制
果然,example 数据文件被该进程调用中
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 282 -> /u01/app/oracle/oradata/PRODCDB/pdbseed/temp012023-04-18_17-51-25-733-PM.dbf
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 283 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf (deleted)
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 284 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/user01.dbf
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 285 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/undotbs01.dbf
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 286 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/sysaux01.dbf
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 287 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/system01.dbf
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 288 -> /u01/app/oracle/oradata/PRODCDB/PDBPROD1/temp012023-04-18_17-51-25-733-PM.dbf
lr-x------. 1 oracle oinstall 64 Jan 10 05:38 3 -> /dev/null
lr-x------. 1 oracle oinstall 64 Jan 10 05:38 4 -> /u01/app/oracle/product/19.3.0/dbhome_1/rdbms/mesg/oraus.msb
lr-x------. 1 oracle oinstall 64 Jan 10 05:38 5 -> /proc/3689/fd
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 6 -> /u01/app/oracle/product/19.3.0/dbhome_1/dbs/hc_PRODCDB.dat
lrwx------. 1 oracle oinstall 64 Jan 10 05:38 7 -> /u01/app/oracle/product/19.3.0/dbhome_1/dbs/lkPRODCDB
复制
原理相同:拷贝文件出来,直接恢复
[oracle@host01 fd]$ cp /proc/3689/fd/283 /u01/app/oracle/oradata/PRODCDB/PDBPROD1/example01.dbf
复制
实操下 Oracle 的数据文件恢复出来不需要修改权限,权限已经改好了,建表验证数据文件可用性:

PG: PG 中万物皆文件,直接查询表对应的文件模拟删除。
testdb=# select oid,datname from pg_database;
oid | datname
-------+-----------
13593 | postgres
16384 | testdb
1 | template1
13592 | template0
24577 | tpcc
(5 rows)
testdb-# \d
List of relations
Schema | Name | Type | Owner
--------+------+-------+----------
public | t1 | table | postgres
(1 row)
testdb=# select oid,relname from pg_class where relname = 't1';
oid | relname
-------+---------
24713 | t1
(1 row)
testdb=# select * from t1;
id
----
1
1
2
1
2
3
(6 rows)
复制
查看在磁盘中该表对应的位置
[postgres@PG 16384]$ pwd
/usr/local/pgsql/data/base/16384
[postgres@PG 16384]$ ll 16385
-rw-------. 1 postgres postgres 8192 Aug 27 06:11 16385
复制
删除该表
[postgres@PG 16384]$ rm -rf 16385
复制
PG 同时也是多进程,我只了解一点 PG 的内存结构,我尝试调用 PG 的 background writer 进程和 checkpointer 都无法在 PROC 目录下找到删除的文件。
思考了一下可能原因如下:
PostgreSQL 的表文件属于惰性删除:当表数据被删除时,PG 会在内部标记这些数据为已删除,但不会立即从磁盘文件中删除这些数据,而是将文件中相关的空间标记为可用。 VACUUM
操作才会真正清理这些标记的空间。 所以进程并不需要一直调用表文件。当删除表文件后,由于该表没有硬链接,删除后 i_nlink 为 0,由于没有进程调用 i_count 也为 0。此时文件被真正的删除了。