暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

Linux文件删除奥秘:无备份如何利用进程恢复删除的数据库表?

kk的DBA随笔 2025-01-10
15

一. Linux 下文件删除过程分析

1. 简介 i_count 和 i_nlink

Linux 的文件系统是通过 link 的数量来控制文件删除的,一般来说,每个文件都有 2 个 link 计数器 icount 和 inlink。

  1. i_count 表示当前文件使用者,或者调用者的数量。
  2. 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 数量

      1.使用 lsof | grep 文件名的形式查看该文件被多少进程引用

        [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
        复制
        2.使用 fuser uv 文件名

        两种方式均可以清楚看到 i_count 为 5。

        五. 什么文件删除能恢复,什么文件不能恢复?

        了解了 icount 和 inlink 的查看方法后,就可以知道什么时候文件能恢复,什么时候不能恢复。

        i_nlink 及 i_count 任意一个不为 0,文件可以直接恢复。

        i_nlink 及 i_count 都为 0 时,文件才会真正的被删除,但也能完全恢复。 

         下图展示了操作系统文件什么时候可以找回,什么时候无法找回。

        一条 rm 命令下来的时候,操作系统做了这些事。

        1. 查找文件对应的 inode
        2. 将 inode 表中指向 A 文件的数据块指针删除 
        3. 在 inode 位图块中将 A 文件 inode 标记未使用
        4. 在块位图块中将 A 文件占用的块标记未使用

        如果此时正好一个新文件过来占用了 A 的块。

        5.磁盘写入新文件
        6.覆盖 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) 20002021, 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: 2No 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。此时文件被真正的删除了。


                                      文章转载自kk的DBA随笔,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

                                      评论