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

PostgreSQL 崩溃后出现的孤立文件

飞象数据 2025-06-16
286

原文地址:

https://www.cybertec-postgresql.com/en/orphaned-files-after-postgresql-crash/

PostgreSQL 以其稳定性而闻名,这毋庸置疑。但这并不意味着它永远不会崩溃。虽然 PostgreSQL 通常会自行清理,但在崩溃后它并没有很好的清理方法(毕竟它丢失了内存)。因此,你的数据目录中可能会出现一些孤立的文件。如果这些文件很小,你可能不会担心。但有时你的数据目录中可能会出现很多垃圾文件。众所周知,解决这个问题非常困难,所以我决定写一篇文章来解决这个问题。

PostgreSQL 为何会崩溃?

如果本文标题让您对 PostgreSQL 产生不好的印象,我会尽力消除您的这种感觉。PostgreSQL 很少崩溃。但有时崩溃并非 PostgreSQL 的错。让我列举一些可能导致崩溃的原因:

  • 你没有禁用内存过量提交,导致内存不足,并且 Linux 内核的内存不足终止程序向其中一个 PostgreSQL 进程发送了 SIGKILL

    https://www.cybertec-postgresql.com/en/what-you-should-know-about-linux-memory-overcommit-in-postgresql/

  • 包含pg_wal
    的文件系统已满, PostgreSQL 无法创建新的WAL段

    https://www.cybertec-postgresql.com/en/why-does-my-pg_wal-keep-growing/

  • PostgreSQL 进程在“关键部分”(修改数据但尚未达到一致性)遇到无法处理的错误 — PostgreSQL 将此类错误升级为PANIC

  • 内存、磁盘或其他硬件组件故障导致数据损坏

  • 管理员无意中损坏了数据库

    https://www.cybertec-postgresql.com/en/how-to-corrupt-your-postgresql-database/

  • PostgreSQL 软件错误导致崩溃或数据损坏(您是否正在运行不受支持的版本或未更新最新的次要版本?)

    https://www.cybertec-postgresql.com/en/upgrading-and-updating-postgresql/

事实上,导致崩溃并留下大量孤立文件的最常见原因是用户运行VACUUM (FULL)
。此命令会创建表及其所有索引的副本。如果磁盘空间不足,而 PostgreSQL 无法创建新的WAL段,数据库服务器就会崩溃。这样的崩溃会留下未完全创建的表副本。为了避免此类问题,我建议您将pg_wal
和数据目录放在不同的文件系统上(同时,也请将日志文件放在单独的文件系统上)。

为什么崩溃会留下孤立文件?

您可能知道 PostgreSQL在崩溃时无法清理,但为什么它在重启时无法清理呢?对此我有两个答案:

  • 遍历所有数据库目录中的所有文件并将其与目录数据和pg_filenode.map
    进行比较,这可能需要很长时间,在此期间服务器尚不可用

  • 目录表中的数据损坏可能导致 PostgreSQL 删除合法数据文件,这将加剧数据损坏

我认为第二点是放弃任何自动清理活动的一个很好的理由。

崩溃后如何识别并删除孤立文件?

这是关键问题。我提供两种方法:

安全的方法:转储并恢复

处理该问题的简单、安全且受支持的方法是:

  • 停止应用程序

  • pg_dump
    数据库到磁盘

  • DROP DATABASE
    — 这将删除所有文件,包括孤立文件

  • 创建一个新的空数据库

  • 将转储恢复到新数据库

  • 启动应用程序

顺便说一句,这也是清理经历过任何形式数据损坏的数据库的正确方法。

虽然这种方式安全又简单,但清理大型数据库可能会导致严重的宕机。如果您觉得无法承受,并且不想采取下文所述的风险路径,那么您应该接受这些孤立文件。它们不会造成任何损害,只会占用部分磁盘空间,而且如今磁盘空间通常比长时间宕机要便宜得多。

危险的方法:识别并删除孤立文件

有几个PostgreSQL 函数允许超级用户检查文件系统

https://www.postgresql.org/docs/current/functions-admin.html

功能描述
pg_ls_dir(dirname)
列出数据库服务器上的目录,返回一个text
pg_stat_file(filename)
返回包含数据库服务器文件各种信息的复合记录,包括大小和修改时间戳
pg_filenode_relation(tablespace, filenode)
返回属于数据库目录中某个文件的关系(表、索引、序列等)的对象 ID(如果不存在这样的关系,则返回 NULL)

通过了解 PostgreSQL 的内部结构,我们可以编写一个针对数据库目录中所有孤立文件的查询:

    WITH d AS (
       /* default tablespace directory for the database */
       SELECT current_setting('data_directory'||
              '/base/' ||
              (SELECT oid FROM pg_database
               WHERE datname = current_database()) AS path
    )
    SELECT split_part(dir.f, '.'1AS filenode,
           /* relation size is the sum of all segments */
           pg_size_pretty(sum(f.size)) AS size,
           max(f.modification) AS last_modified
    FROM d
       /* all the files in the database directory */
       CROSS JOIN LATERAL pg_ls_dir(d.path) AS dir(f)
       /* file data like size and modification timestamp */
       CROSS JOIN LATERAL pg_stat_file(d.path || '/' || dir.f) AS f
    WHERE /* file name is digits only, optionally followed by a dot and more digits */
          /* that means: a segment of the "main fork" of a relation */
          f ~ '^\d+(\.\d+)*$'
      /* PostgreSQL doesn't know a relation that belongs to the file */
      AND pg_filenode_relation(0::oid, split_part(dir.f, '.'1)::bigint::oid) IS NULL
    GROUP BY filenode;
    要删除查询返回的文件节点(假设我们得到 12345),请转到数据库目录:
      SELECT current_setting('data_directory'||
             '/base/' ||
             (SELECT oid FROM pg_database
              WHERE datname = current_database());
      并删除属于它的文件:
        rm 12345*

        于删除孤立文件的警告

        删除文件时要小心:

        • 上述查询仅会识别默认表空间中的文件。如果您创建了其他表空间,则查询将无法在这些表空间中找到孤立文件。

        • 不要删除最近的文件。并发事务可能已经创建了这些文件,而目录条目尚未对您的查询可见。

        • 最好在崩溃发生后等待一两周。然后,仅删除查询返回的、在崩溃时最后修改的文件。

        • 如果查询返回一些小文件,请不要删除它们。它们不会给您带来麻烦,也不值得冒险。

        在删除数据目录中的任何文件之前,请确保您已做好备份。

        结论

        崩溃后清除孤立文件的安全方法是转储并恢复数据库。对于那些胆子更大的朋友,我提供了一个查询,可以帮助你识别这些孤立文件。

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

        评论