与所有其他关系数据库系统一样,PostgreSQL需要通过写入wal日志或在Checkpoint时同步数据到数据文件来持久化数据到磁盘上。对于数据文件,一旦Relation达到SEGMENT_SIZE(默认1GB),PostgreSQL就会创建一个新的数据文件。因此如果Relation持续增长,则该Relation可能会由多个文件组成。在这篇文章中想要考虑的问题是,是否可能存在孤儿文件。如果文件不是任何Relation的组成部分或引用,但该文件仍在磁盘上,则该文件将成为孤儿文件。一方面会浪费磁盘空间,另一方面,也会与PostgreSQL Catalog中存储的内容不一致。
先建一个空表:
postgres=# createtable t1 ( a int);CREATETABLE
表创建好以后会在磁盘上创建一个文件,可以通过查询PostgreSQL得到相关的文件信息:
postgres=# select pg_relation_filepath('t1'); pg_relation_filepath ---------------------- base/12724/24577(1 row)
可以通过操作系统的命令查看文件信息:
postgres@db ls-la$PGDATA/base/12724/24577 -rw-------. 1 postgres postgres 0 Nov 1316:53 /u02/pgdata/14/base/12724/24577
现在表中还没有任何内容,所以数据文件是空的。PostgreSQL提供了一个叫oid2name的小工具,可用来定位指定Relation的位置。
postgres@db oid2name -t t1 -xFrom database "postgres": Filenode Table Name Oid Schema Tablespace -------------------------------------------------24577 t1 24577 public pg_default
一旦我们开始向Relation中插入数据,对应的数据文件就开始增长,如果其尺寸达到segment_size,PostgreSQL将为Relation添加一个新文件(这里要注意,我的segment_size 设置成了2GB,而不是缺省的1GB):
postgres=# insertinto t1 select*from generate_series(1,1000000);INSERT01000000postgres=# ! ls -lha $PGDATA/base/12724/24577-rw-------. 1 postgres postgres 35M Nov 13 17:03 /u02/pgdata/14/base/12724/24577postgres=# insertinto t1 select*from generate_series(1,100000000);INSERT0100000000postgres=# ! ls -la $PGDATA/base/12724/24577*-rw-------. 1 postgres postgres 2147483648 Nov 13 17:07 /u02/pgdata/14/base/12724/24577-rw-------. 1 postgres postgres 1513545728 Nov 13 17:08 /u02/pgdata/14/base/12724/24577.1-rw-------. 1 postgres postgres 917504 Nov 13 17:07 /u02/pgdata/14/base/12724/24577_fsm
*_fsm文件是自由空间映射(free space map),用于跟踪数据文件的可用空间。
现在已经清楚了磁盘上发生了什么,我们将回到本文的第一个问题:磁盘上的文件会不会不属于任何Relation?考虑这个例子:在一个会话中,我们启动了一个新事务并创建了一个空表,但没有提交该事务。同时我们先取得会话ID和数据文件在磁盘上的位置:
postgres=# begin;BEGINpostgres=# createtable t2 ( a int);CREATETABLEpostgres=# select pg_relation_filepath('t2'); pg_relation_filepath ---------------------- base/12724/24580(1 row)postgres=# select*from pg_backend_pid(); pg_backend_pid ----------------7170(1 row)
事务尚未提交,但我们已经可以在磁盘上看到相关文件了,PostgreSQL已经创建了它:
postgres=# ! ls -la $PGDATA/base/12724/24580-rw-------. 1 postgres postgres 0 Nov 13 17:17 /u02/pgdata/14/base/12724/24580
如果此时服务器挂了或者该会话OOM被杀掉了,会发生什么?我们可以用kill -9 PID来模拟杀掉该会话:
postgres@db kill -97170
返回刚才的psql会话,发现连接已丢失,但会立即重连:
postgres-# select1;server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request. The connection to the server was lost. Attempting reset: Succeeded. postgres=# select1; ?column? ----------1(1 row)
总之,在通过事务创建表并且在事务被提交前终止会话,这时事务必须被回滚,表也不应该存在:
postgres=# select*from t2;ERROR: relation "t2" does not exist LINE 1:select*from t2;
这很好,也是我们所期待的。但我们还能看到磁盘上的文件吗?
postgres@db ls -lha $PGDATA/base/12724/24580-rw-------. 1 postgres postgres 0 Nov 13 17:17 /u02/pgdata/14/base/12724/24580
现在我们有了一个不属于PostgreSQL已知的任何Relation的孤儿文件:
postgres=# select relname from pg_class where oid ='24580'; relname ---------(0 rows)
因此,可能会出现需要清理磁盘上的文件的情况。想象一下,当你向Relation中导入大量数据,就在导入即将完成前前,会话被终止了:
postgres=# begin;BEGINpostgres=# createtable t3 ( a int);CREATETABLEpostgres=# select pg_relation_filepath('t3'); pg_relation_filepath ---------------------- base/12724/32769(1 row)postgres=# select*from pg_backend_pid(); pg_backend_pid ----------------7577(1 row)postgres=# insertinto t3 select*from generate_series(1,10000000);server closed the connection unexpectedly This probably means the server terminated abnormally before or while processing the request.
现在文件不再为空,磁盘空间被占用:
postgres@db ls -lha $PGDATA/base/12724/32769-rw-------. 1 postgres postgres 235M Nov 13 17:42 /u02/pgdata/12/base/12724/32769
在最糟糕的情况下,这可能会浪费数GB或TB的空间。有方法可以检测这种孤儿文件吗?您需要将PostgreSQL记录在目录中的内容与文件系统进行比较,然后删除所有PostgreSQL不知道的内容,这需要非常非常小心地完成。
首先,需要获取要检查的数据库的OID:
postgres=# select oid from pg_database where datname ='postgres'; oid -------12724(1 row)
完成后,就知道了其在磁盘上的位置,即$PGDATA/base/[数据库OID](这里没考虑表空间)。从这里开始,你可以列出应该属于某个Relation的所有文件:
postgres=# select*from pg_ls_dir ('/u02/pgdata/14/base/12724')as file where file ~'^[0-9]*'file -------1255124712491259...
所有这些都应该在pg_class中有对应的条目(否则PostgreSQL不知道它们)。
最后,获取孤儿文件的列表:
select*from pg_ls_dir ('/u02/pgdata/14/base/12724')as file where file ~'^[0-9]*'and file::textnotin(select relfilenode::textfrom pg_class);file ------- 163852458032769(3 rows)
这就是你需要仔细检查的文件。如果您确定它是一个孤儿文件,你可以将其删除(当然,删除前一定要先备份)。