PostgreSQL执行TRUNCATE或DROP TABLE命令后磁盘空间未释放

在PostgreSQL中,使用TRUNCATE命令清理表数据,或使用DROP TABLE删除表时,可能出现磁盘空间未立即释放的问题。本文将介绍该现象的原因,并提供排查和解决方法。

问题原因

PostgreSQL执行TRUNCATE和DROP TABLE操作时,系统会在事务提交时对每一个要删除的文件进行unlink调用。unlink调用会解除inode的引用,但是如果此时有其他进程打开了该文件,文件的内容将会保留在磁盘上,磁盘空间不会立即回收。只有当满足以下条件之一时,系统才会释放磁盘空间:

  • 终止所有打开了需要删除文件的进程。

  • 打开文件的进程中,对文件描述符(file descriptor,fd)进行了close调用来关闭对应文件。

如果在执行TRUNCATE和DROP TABLE之前已经有连接打开了表文件,并且该连接在TRUNCATE和DROP TABLE执行后一直处于空闲(idle)状态,那么由于这个连接仍然持有对原始文件的打开引用,实际的磁盘空间不会立即被释放。

解决方案

在出现执行TRUNCATE或DROP TABLE命令后未释放磁盘空间的问题时,可以采用以下方法进行解决。

  • 清理所有空间的连接。

    SELECT pg_terminate_backend(pid)
    FROM pg_stat_activity
    WHERE state='idle' AND backend_type='client backend';
  • 在自建数据库场景中,需要找到已经打开但已被unlink的文件的进程,并终止这些连接。

    1. 在目标数据库服务器上执行lsof命令,查找已经被删除但仍被进程打开的文件。

      lsof +L1
    2. 执行如下SQL,终止对应连接。

      SELECT pg_terminate_backend(pid);

      其中参数pid是要终止会话的进程ID。