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

pg_table_size计算的值大于pg_relation_size各分支的累计值,认知哪儿错了

399

pg_table_size计算的值大于pg_relation_size各分支的累计值,认知哪儿错了?

在前段时间验证toast相关功能的过程中,遇到一个让我疑惑的问题,就是pg_table_size计算的值大于pg_relation_size各分支的累计值并且计算的分支也包括了toast表的分支。如果你对Postgresql中计算表或对象大小函数有一定了解的话,我相信你看到到一些资料,可能会有类似描述“pg_relation_size各分支大小合计等于pg_table_size的值”,那么在我的测试验证过程中,这个差值到底是哪部分分支未计算到呢,下面我们来一起深入研究一下。

函数功能介绍

在测试前,我们先来了解一下上面的两个函数在官网文档中的介绍,具体的定义如下:

pg_table_size

pg_table_size ( regclass ) → bigint
计算指定表所使用的磁盘空间,不包括索引(但包括它的TOAST表,如果有的话,空闲空间映射,以及可见性映射)。

在这个函数中,我们需要注意的是pg_table_size的大小是不包括索引,但是包括它的TOAST表,因此在下面的验证计算的过程中,也分别计算了TOAST表的大小。

pg_relation_size

pg_relation_size ( relation regclass [, fork text ] ) → bigint
计算指定关系的一个“fork”所使用的磁盘空间。 (注意在大多数情况下,使用更高级的函数 pg_total_relation_size或pg_table_size更方便,它们将所有分叉(forks)的大小相加。) 使用一个参数,这将返回关系的主数据分叉的大小。第二个参数可以用来指定要检查哪个分叉:
main返回关系的主数据分叉的大小。
fsm 返回与该关系关联的空闲空间映射(参见第 73.3 节)的大小。
vm 返回与该关系相关联的可见性映射(参见第 73.4 节)的大小。
init 返回初始化分叉的大小,如果有的话,与关系相关。

在这个函数中,需要注意的pg_relation_size为某一个分支的大小,默认计算main分支的大小。

准备测试用例

创建测试用例表

create table tbl_blog (id serial primary key,author varchar(50),title varchar(200),content text);

执行命令插入数据

insert into tbl_blog(author,title,content) values('墨竹','推荐Postgresql中一些好用的psql命令','psql客户端工具应该是dba非常频繁使用的的工具。');

计算表大小并查看结果

在下面的SQL中,利用pg_relation_size函数分别计算了各个分支的大小。另外也需要注意,对于toast表的大小也是由各个分支的组成的,因此在验证的过程,也分别计算了各个分支。

select main,vm,fsm,init,main+vm+fsm+init as table_sum_size,table_size,
toast_main_size,toast_fsm_size,toast_vm_size,toast_init_size,
toast_main_size+toast_fsm_size+toast_vm_size+toast_init_size as toast_sum_size,toast_size
from (
select pg_relation_size(oid::regclass,'main') as main,
pg_relation_size(oid::regclass,'vm') as vm,
pg_relation_size(oid::regclass,'fsm') as fsm,
pg_relation_size(oid::regclass,'init') as init,
pg_table_size(oid::regclass) as table_size,
pg_relation_size(reltoastrelid,'main') as toast_main_size,
pg_relation_size(reltoastrelid,'fsm') as toast_fsm_size,
pg_relation_size(reltoastrelid,'vm') as toast_vm_size,
pg_relation_size(reltoastrelid,'init') as toast_init_size,
pg_table_size(reltoastrelid) as toast_size
 from pg_class where relname='tbl_blog');

上面的SQL执行的结果如下:

-[ RECORD 1 ]---+------
main            | 8192
vm              | 0
fsm             | 0
init            | 0
table_sum_size  | 8192
table_size      | 16384
toast_main_size | 0
toast_fsm_size  | 0
toast_vm_size   | 0
toast_init_size | 0
toast_sum_size  | 0
toast_size      | 0

Time: 1.615 ms

从执行结果,可以看到pg_table_size计算的值为16384,表的各分支计算的结果8192,toast表的各分支累计值为0,在这里我们就可以看到pg_table_size比pg_relation_size计算的值大8192字节,这也就是我们在文章开头中提到的问题。

分析过程

在发现上面的问题之后,其实我已经反复的核对这两个函数分别计算的内容,也确认所有的已知的对象都已经计算了,对于目前存在的差异,也很明显是有部分对象未计算。在通过查找网上的资料未果后,也只能去看PG的源码中pg_table_size函数的定义,看看是否包含一些未知内容。在dbsize.c文件中,找到pg_table_size函数的定义,如下图:

image20241209144146266.png

在上图pg_table_size函数定义中,可以看到在函数中又调用calculate_table_size计算的大小,下面继续看看这个函数的说明。

image20241206151714194.png

在这里看到calculate_table_size需要计算两个分支大小,一个是表的大小(heap size, including FSM and VM),一个是toast 的大小(Size of toast relation)。我继续看了这个函数的说明。

 * Calculate total on-disk size of a given table,
 * including FSM and VM, plus TOAST table if any.
 * Indexes other than the TOAST table's index are not included.
 *
 * Note that this also behaves sanely if applied to an index or toast table;
 * those won't have attached toast tables, but they can have multiple forks.
 */

简单的翻译后,描述的是”计算给定表(包括FSM和VM)的总磁盘大小,如果有的话,再加上TOAST表。不包括TOAST表索引以外的索引“,在这里可以看到两个关键信息,**一个是TOAST表索引,说实话这个还是第一接触这个概念;另外一个是TOAST表索引的值是被包含在calculate_table_size函数计算的值内,即pg_table_size也包含该索引。**既然看到可能有问题的地方,那就接着往下看看TOAST大小计算相关的代码中还有什么有用的信息不。

image20241206153603403.png

在这里,就可以清晰看到清晰toast表的大小包括两部分内容,一个是toast堆表,另外一个是toast索引大小,也包括fsm和vm分支大小。看到这里,大概已经知道之前没有计算的部分,可能是toast索引大小。下面就尝试找找这个toast索引。

下面的SQL是后整理的,查询的测试表tbl_blog对应toast表及其toast索引信息,这样可以方便大家直接查看最终的汇总的信息。

--indrelid为toast表对应的oid
select pc.relname as table_name,pc1.relname as toast_name,
pc2.relname as toast_idx_name,pc2.oid idx_oid,pc2.relpages from pg_class pc
join pg_class pc1 on pc.reltoastrelid= pc1.oid
join pg_index pi on pi.indrelid=pc.reltoastrelid
join  pg_class pc2 on pc2.oid=pi.indexrelid 
where pc.relname='tbl_blog';

 table_name |   toast_name   |    toast_idx_name    | idx_oid | relpages
------------+----------------+----------------------+---------+----------
 tbl_blog   | pg_toast_33048 | pg_toast_33048_index |   33053 |        1
(1 row)

Time: 0.619 ms

在上面的结果信息可以知道,这个toast表对应的索引名称为pg_toast_33048_index,其OID为33053。另外也看到relpages为1,大小应该就是8192正好是刚才的差值。

计算toast表索引大小

select 
pg_relation_size(33053,'main') as toast_idx_main_size,
pg_relation_size(33053,'fsm') as toast_idx_fsm_size,
pg_relation_size(33053,'vm') as toast_idx_vm_size,
pg_relation_size(33053,'init') as toast_idx_init_size,
pg_table_size(33053) as toast_size;

 toast_idx_main_size | toast_idx_fsm_size | toast_idx_vm_size | toast_idx_init_size | toast_size
---------------------+--------------------+-------------------+---------------------+------------
                8192 |                  0 |                 0 |                   0 |       8192
(1 row)

Time: 0.317 ms

从计算结果,可以知道toast表索引大小为8192,就是刚才的差值。其实这个我们也可以直接服务器上数据文件33053的的大小,如图为8192字节。

image20241206165512104.png

总结

虽然这个问题很简单,但是这个知识点隐藏的很深,如果不看代码,大概率是找不到原因,是由于toast表上默认就有索引存在并占用存储空间,即使toast表是没有值的情况下,而我们之前在验证的时候就未计算这部分toast表索引分支,因此导致两个比值存在差异。最后我就直接借用下面的这个图片,重新对几个函数的关系梳理一下。

image-20241206165512104

1、pg_relation_size用于计算OID代表的表或者索引所使用的磁盘空间,当然这里也可以用于计算toast表及其索引大小,需要注意默认是计算的main分支的大小。

2、pg_indexes_size用于计算表的索引使用的总磁盘空间。

3、pg_table_size用于计算表使用的磁盘空间,不计索引(但是包含TOAST表与TOAST表索引,)

4、pg_total_relation_size用于表使用的总磁盘空间,包括所有的索引和TOAST数据

pg_table_size=pg_relation_size(表的分支)+pg_relation_size(toast表的分支)+pg_relation_size(toast表索引的分支)

pg_total_relation_size=pg_table_size+pg_indexes_size

最后,为了避免在日常工作对于表大小计算出现差错,建议大家尽可能的使用pg_table_size或pg_total_relation_size函数来计算表的大小。

参考

https://dba.stackexchange.com/questions/23879/measure-the-size-of-a-postgresql-table-row/23933#23933

https://www.modb.pro/db/1813145401399193600

– / END / –

可以通过下面的方式联系我

  • 微信公众号:@墨竹札记
  • 墨天轮:@墨竹
  • 微信:wshf395062788
  • PGFans:@墨竹

如果这篇文章为你带来了灵感或启发,就请帮忙点赞收藏转发;如果文章中不严谨或者错漏之处,请及时评论指正。非常感谢!

最后修改时间:2025-07-01 16:42:20
「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论