获取表大小
- 概述
-
- [pg_table_size 函数](#pg_table_size 函数)
- [calculate_table_size 函数](#calculate_table_size 函数)
- [calculate_table_file_size 函数](#calculate_table_file_size 函数)
- [CalculateCStoreRelationSize 函数](#CalculateCStoreRelationSize 函数)
- [calculate_relation_size 函数](#calculate_relation_size 函数)
声明 :本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss1.1.0 的开源代码和《OpenGauss数据库源码解析》和《PostgresSQL数据库内核分析》一书
概述
在 OpenGauss 中,有一个名为 pg_table_size 的内置函数可以帮助我们实时获取表的存储空间大小 。本文将深入探讨这个函数的工作原理、用途以及如何使用它来监控和优化数据库性能。pg_table_size 函数的使用方式如下所示:
sql
select pg_size_pretty(pg_table_size('表名'));
下面我们以一个实际的案例来调试下:
sql
---创建列存表
CREATE TABLE column_store_table (
id INT,
name VARCHAR(50),
age INT,
salary DECIMAL(10, 2),
email VARCHAR(100)
)WITH (ORIENTATION = COLUMN);
---插入数据
INSERT INTO column_store_table VALUES
(1, 'John', 30, 50000.00, 'john@example.com'),
(2, 'Alice', 28, 60000.50, NULL),
(3, 'Bob', NULL, NULL, 'bob@example.com');
---执行pg_table_size查看表的大小
select pg_size_pretty(pg_table_size('column_store_table'));
pg_size_pretty
----------------
72 kB
(1 row)
pg_table_size 函数
pg_table_size 函数作用是根据输入的表对象标识符 (Oid ),获取该表的大小信息。在获取表大小时,根据数据库是否为分布式数据库 (PGXC )以及其他条件,它可能会采用不同的计算方法。最终,函数将计算得到的表大小 作为整数值返回 。如果表无效(不存在或无法打开),则返回 NULL 。这个函数的目的是为了提供数据库管理员 或开发人员 一个快速获取表大小的工具 ,以便进行性能监控 和优化 。函数源码如下所示:(路径:src\common\backend\utils\adt\dbsize.cpp
)
cpp
Datum pg_table_size(PG_FUNCTION_ARGS)
{
// 从参数中获取传入的表的对象标识符
Oid relOid = PG_GETARG_OID(0);
// 表关联对象
Relation rel = NULL;
int64 size;
/* 获取共享锁,以防止其他并行操作中删除该表 */
// 尝试以访问共享锁的方式打开指定的表,并将其关联到 rel 变量中
rel = try_relation_open(relOid, AccessShareLock);
// 如果表无效(不存在或无法打开)
if (!RelationIsValid(rel)) {
// 返回 NULL,表示无法获取表的大小
PG_RETURN_NULL();
}
#ifdef PGXC
// 如果是分布式数据库(PGXC)
if (COLLECT_FROM_DATANODES(relOid)) {
// 通过调用 pgxc_exec_sizefunc 从分布式数据节点收集表的大小信息
// 结果存储在 size 变量中
size = pgxc_exec_sizefunc(relOid, "pg_table_size", NULL);
} else {
#else
// 如果不是分布式数据库(PGXC)或分布式功能未启用
if (true) {
#endif
// 使用 calculate_table_size 函数计算表的大小
// 具体的实现可能包括扫描表的存储文件、统计页面数等等
// 计算结果存储在 size 变量中
size = calculate_table_size(rel, DEFAULT_FORKNUM);
}
// 关闭之前打开的表,并释放占用的资源
relation_close(rel, AccessShareLock);
rel = NULL;
// 返回计算得到的表的大小,以整数的形式返回
PG_RETURN_INT64(size);
}
calculate_table_size 函数
calculate_table_size 函数主要功能是计算指定表(或其索引 、TOAST 表等)的总存储空间大小。这个大小包括表的主要数据文件、Free Space Map (FSM ,用于跟踪空闲块的数据结构)、Visibility Map (VM ,用于加速查询的数据结构)以及 TOAST 表(如果存在)。该函数还能够处理分区表 ,并且能够针对列存储表 执行不同的计算。函数源码如下所示:(路径:src\common\backend\utils\adt\dbsize.cpp
)
cpp
/*
* 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.
*/
static int64 calculate_table_size(Relation rel, int forkNumOption)
{
int64 size = 0; // 初始化变量 size,用于存储表的总存储空间大小
// 检查表是否是索引,如果不是索引则进入下面的逻辑
if (!RelationIsIndex(rel)) {
#ifdef ENABLE_MOT
// 如果表是分布式表且是 MOT 存储引擎的表
if (RelationIsForeignTable(rel) && RelationIsMOTTableByOid(RelationGetRelid(rel))) {
// 调用 CalculateMotRelationSize 函数计算表的大小
size = CalculateMotRelationSize(rel, InvalidOid);
} else if (!RelationIsPartitioned(rel)) {
#else
// 如果不是分布式表,进入下面的逻辑
if (!RelationIsPartitioned(rel)) {
#endif
// 调用 calculate_table_file_size 函数计算表的文件大小
// 具体的计算方式包括扫描数据文件、FSM、VM等
size = calculate_table_file_size(rel, RelationIsColStore(rel), forkNumOption);
} else {
List* partitions = NIL;
ListCell* cell = NULL;
Partition partition = NULL;
Relation partRel = NULL;
// 获取分区表的分区列表
partitions = relationGetPartitionList(rel, AccessShareLock);
// 迭代处理每个分区,计算每个分区的文件大小,并累加到 size 变量中
foreach (cell, partitions) {
partition = (Partition)lfirst(cell);
partRel = partitionGetRelation(rel, partition);
// 调用 calculate_table_file_size 函数计算分区的文件大小
// 具体的计算方式与非分区表相似
size += calculate_table_file_size(partRel, RelationIsColStore(rel), forkNumOption);
// 释放分区关联的资源
releaseDummyRelation(&partRel);
}
// 释放分区列表的资源
releasePartitionList(rel, &partitions, AccessShareLock);
#ifdef ENABLE_MULTIPLE_NODES
// 如果表是分布式时序存储表,额外计算时序标签表的大小
if (RelationIsTsStore(rel)) {
size += CalculateTsTagRelationSize(rel, forkNumOption);
}
#endif
}
} else {
// 如果表是索引,找到索引对应的基表
Relation baseRel = relation_open(rel->rd_index->indrelid, AccessShareLock);
bool bCstore = RelationIsColStore(baseRel) && (rel->rd_rel->relam == PSORT_AM_OID);
#ifdef ENABLE_MOT
// 如果基表是分布式表且是 MOT 存储引擎的表
if (RelationIsForeignTable(baseRel) && RelationIsMOTTableByOid(RelationGetRelid(baseRel))) {
// 调用 CalculateMotRelationSize 函数计算表的大小
size = CalculateMotRelationSize(baseRel, RelationGetRelid(rel));
} else if (!RelationIsPartitioned(rel)) {
#else
if (!RelationIsPartitioned(rel)) {
#endif
// 如果基表不是列存储表
if (!bCstore)
// 调用 calculate_table_file_size 函数计算索引文件的大小
size = calculate_table_file_size(rel, false, forkNumOption);
else {
// 如果基表是列存储表,找到列存储索引对应的表,计算该表的文件大小
Relation cstoreIdxRel = relation_open(rel->rd_rel->relcudescrelid, AccessShareLock);
if (cstoreIdxRel != NULL) {
size = calculate_table_file_size(cstoreIdxRel, true, forkNumOption);
relation_close(cstoreIdxRel, AccessShareLock);
}
}
} else {
List* partOids = NIL;
ListCell* cell = NULL;
Oid partOid = InvalidOid;
Oid partIndexOid = InvalidOid;
Partition partIndex = NULL;
Relation partIndexRel = NULL;
Relation cstorePartIndexRel = NULL;
// 获取基表的分区 OID 列表
partOids = relationGetPartitionOidList(baseRel);
// 迭代处理每个分区
foreach (cell, partOids) {
partOid = lfirst_oid(cell);
// 找到分区对应的索引 OID
partIndexOid = getPartitionIndexOid(RelationGetRelid(rel), partOid);
partIndex = partitionOpen(rel, partIndexOid, AccessShareLock);
partIndexRel = partitionGetRelation(rel, partIndex);
// 如果基表是列存储表
if (bCstore) {
// 找到列存储索引对应的表,计算该表的文件大小
cstorePartIndexRel = relation_open(partIndexRel->rd_rel->relcudescrelid, AccessShareLock);
size += calculate_table_file_size(cstorePartIndexRel, true, forkNumOption);
relation_close(cstorePartIndexRel, AccessShareLock);
} else
// 如果基表不是列存储表,计算索引文件的大小
size += calculate_table_file_size(partIndexRel, false, forkNumOption);
// 释放分区相关的资源
partitionClose(rel, partIndex, AccessShareLock);
releaseDummyRelation(&partIndexRel);
}
// 释放分区 OID 列表的资源
releasePartitionOidList(&partOids);
#ifdef ENABLE_MULTIPLE_NODES
// 如果表是分布式时序存储表,额外计算时序标签表的大小
if (RelationIsTsStore(rel)) {
size += CalculateTsTagRelationSize(rel, forkNumOption);
}
#endif
}
// 关闭基表
relation_close(baseRel, AccessShareLock);
}
// 返回计算得到的表的大小
return size;
}
代码主要逻辑:
- 首先,代码定义了一个 int64 类型的变量 size ,用于存储计算得到的表的大小。
- 代码通过检查表的类型 来决定如何计算表的大小 。如果表不是索引 ,会进入下面的逻辑分支:
- 如果表是分布式表 (PGXC ),则调用 CalculateMotRelationSize 函数计算表的大小。
- 如果表不是分布式表 ,会根据表是否是分区表执行不同的逻辑。如果不是分区表 ,则调用 calculate_table_file_size 函数计算表的文件大小 。如果是分区表 ,则迭代处理每个分区 ,计算每个分区的文件大小,并累加到 size 变量中 。如果表是时序存储表,则额外计算时序标签表的大小。
- 如果表是索引 ,代码会首先找到索引对应的基表 ,然后根据基表是否是列存储表 执行不同的逻辑:
- 如果基表不是列存储表 ,则调用 calculate_table_file_size 函数计算索引文件的大小。
- 如果基表是列存储表 (同时要求索引使用 PSORT_AM ),则找到列存储索引对应的表,计算该表的文件大小。
- 最后,函数返回计算得到的表的大小。
总的来说,这段代码提供了一个重要的功能,用于计算表的存储空间大小 ,包括各种相关文件的大小。这对于数据库管理员和性能优化来说非常有用,可以帮助他们更好地管理数据库存储和性能。
calculate_table_file_size 函数
calculate_table_file_size 的主要功能是计算数据库中指定表的总文件大小 ,包括主表 和相关的分桶表 、列存储表 、时序存储表 等。它通过检查表的特性 和指定的叉号 选项来决定计算哪些文件的大小 ,并将这些大小累加到一个变量中 ,最终返回表的总文件大小 。这个功能对于数据库管理员和性能优化是非常有用的,可以帮助他们了解表的实际存储需求以及数据库的性能特征,从而更好地管理和优化数据库系统。函数源码如下所示:(路径:src\common\backend\utils\adt\dbsize.cpp
)
cpp
static int64 calculate_table_file_size(Relation rel, bool isCStore, int forkNumOption)
{
int64 size = 0; // 初始化变量 size,用于存储表的文件大小
ForkNumber forkNum = (ForkNumber)0; // 初始化 forkNum,表示文件叉号
// 如果表启用了分桶
if (RELATION_CREATE_BUCKET(rel)) {
Relation heapBucketRel = NULL;
oidvector* bucketlist = searchHashBucketByOid(rel->rd_bucketoid);
// 迭代处理每个分桶
for (int i = 0; i < bucketlist->dim1; i++) {
heapBucketRel = bucketGetRelation(rel, NULL, bucketlist->values[i]);
if (forkNumOption == DEFAULT_FORKNUM) {
/*
* 计算堆关系的大小,包括空闲块空间映射(FSM)和可见性映射(VM)
*/
for (int ifork = 0; ifork <= MAX_FORKNUM; ifork++) {
forkNum = (ForkNumber)ifork;
size += calculate_relation_size(&(heapBucketRel->rd_node), InvalidBackendId, forkNum);
}
} else {
// 如果指定了 forkNumOption,计算指定叉号的堆关系大小
size += calculate_relation_size(&(heapBucketRel->rd_node), InvalidBackendId, forkNumOption);
}
// 关闭分桶关系
bucketCloseRelation(heapBucketRel);
}
/*
* 计算 TOAST 表的大小
*/
if (OidIsValid(rel->rd_rel->reltoastrelid)) {
size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
}
} else {
if (forkNumOption == DEFAULT_FORKNUM) {
/*
* 计算堆关系的大小,包括空闲块空间映射(FSM)和可见性映射(VM)
*/
for (int ifork = 0; ifork <= MAX_FORKNUM; ifork++) {
forkNum = (ForkNumber)ifork;
// 如果是列存储表(CStore),调用 CalculateCStoreRelationSize 计算大小
if (isCStore) {
size += CalculateCStoreRelationSize(rel, forkNum);
}
#ifdef ENABLE_MULTIPLE_NODES
// 如果是分布式时序存储表,调用 CalculateTStoreRelationSize 计算大小
else if (RelationIsTsStore(rel)) {
size += CalculateTStoreRelationSize(rel, forkNum);
}
#endif
else {
// 否则,调用 calculate_relation_size 计算大小
size += calculate_relation_size(&(rel->rd_node), rel->rd_backend, forkNum);
}
}
/*
* 计算 TOAST 表的大小
*/
if (OidIsValid(rel->rd_rel->reltoastrelid)) {
size += calculate_toast_table_size(rel->rd_rel->reltoastrelid);
}
} else {
// 如果指定了 forkNumOption,计算指定叉号的关系大小
if (isCStore) {
size += CalculateCStoreRelationSize(rel, forkNumOption);
} else if (RelationIsTsStore(rel)) {
#ifdef ENABLE_MULTIPLE_NODES
// 如果是分布式时序存储表,调用 CalculateTStoreRelationSize 计算大小
size += CalculateTStoreRelationSize(rel, forkNumOption);
#endif
} else {
// 否则,调用 calculate_relation_size 计算大小
size += calculate_relation_size(&(rel->rd_node), rel->rd_backend, forkNumOption);
}
}
}
return size; // 返回计算得到的表的文件大小
}
CalculateCStoreRelationSize 函数
CalculateCStoreRelationSize 函数的主要功能是计算数据库中列存储表(CStore)的总文件大小 ,包括数据文件 、列存储文件 、元数据文件 和增量表的相关文件 。它通过迭代处理每个数据文件 和列存储文件的段 来计算文件的大小,并考虑增量表 和 CUDesc 表 的大小,最终返回列存储表的总文件大小。这个功能对于数据库管理员和性能优化人员非常有用,可以帮助他们了解列存储表的存储需求,进行性能分析和数据库优化。函数源码如下所示:(路径:src\common\backend\utils\adt\dbsize.cpp
)
cpp
int64 CalculateCStoreRelationSize(Relation rel, ForkNumber forknum)
{
int64 totalsize = 0; // 初始化变量 totalsize,用于存储计算得到的列存储表总文件大小
int64 size = 0; // 初始化变量 size,用于暂存中间计算结果
uint64 segcount = 0; // 初始化变量 segcount,用于迭代列存储文件段
char pathname[MAXPGPATH] = {'\0'}; // 初始化数组 pathname,用于存储文件路径信息
// 如果指定的叉号是主叉号(MAIN_FORKNUM)
if (forknum == MAIN_FORKNUM) {
/*
* 计算数据文件大小。
*/
if (RelationIsDfsStore(rel)) { // 如果表是 DFS 存储表
if (IS_PGXC_DATANODE && IsConnFromApp()) {
/*
* 计算 HDFS 上 ORC 格式的数据文件大小(仅在连接来自应用程序时计算)。
*/
size = getDFSRelSize(rel); // 调用 getDFSRelSize 函数计算数据文件大小
totalsize += size; // 累加到 totalsize 中
}
} else { // 如果表不是 DFS 存储表
// 针对表的每个属性(列)进行处理
for (int i = 0; i < RelationGetDescr(rel)->natts; i++) {
totalsize += calculate_relation_size(
&rel->rd_node, rel->rd_backend, ColumnId2ColForkNum(rel->rd_att->attrs[i]->attnum));
// 调用 calculate_relation_size 计算数据文件大小,并累加到 totalsize 中
// 创建列存储文件节点对象
CFileNode tmpNode(rel->rd_node, rel->rd_att->attrs[i]->attnum, MAIN_FORKNUM);
// 创建 CUStorage 对象用于操作列存储文件
CUStorage custore(tmpNode);
// 迭代处理每个段(segment)
for (segcount = 0;; segcount++) {
struct stat fst;
CHECK_FOR_INTERRUPTS(); // 检查是否有中断请求
custore.GetFileName(pathname, MAXPGPATH, segcount); // 获取段的文件路径
if (stat(pathname, &fst) < 0) { // 获取文件状态信息
if (errno == ENOENT) // 如果文件不存在,跳出循环
break;
else
ereport(
ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathname)));
// 报告文件访问错误
}
totalsize += fst.st_size; // 累加段文件大小到 totalsize 中
}
custore.Destroy(); // 销毁 CUStorage 对象
}
}
}
// 添加增量表(delta table)的大小
Relation deltaRel = try_relation_open(rel->rd_rel->reldeltarelid, AccessShareLock);
if (deltaRel != NULL) {
totalsize += calculate_relation_size(&(deltaRel->rd_node), deltaRel->rd_backend, forknum);
// 调用 calculate_relation_size 计算增量表大小,并累加到 totalsize 中
// 仅当指定的叉号为主叉号时,计算增量表的 TOAST 表大小,并累加到 totalsize 中
if (MAIN_FORKNUM == forknum && OidIsValid(deltaRel->rd_rel->reltoastrelid)) {
totalsize += calculate_toast_table_size(deltaRel->rd_rel->reltoastrelid);
}
relation_close(deltaRel, AccessShareLock); // 关闭增量表关系
}
// 添加 CUDesc 表(CUDesc table)的大小
Relation cudescRel = try_relation_open(rel->rd_rel->relcudescrelid, AccessShareLock);
if (cudescRel != NULL) {
totalsize += calculate_relation_size(&(cudescRel->rd_node), cudescRel->rd_backend, forknum);
// 调用 calculate_relation_size 计算 CUDesc 表大小,并累加到 totalsize 中
// 仅当指定的叉号为主叉号时,计算 CUDesc 表的 TOAST 表大小,并累加到 totalsize 中
if (MAIN_FORKNUM == forknum && OidIsValid(cudescRel->rd_rel->reltoastrelid)) {
totalsize += calculate_toast_table_size(cudescRel->rd_rel->reltoastrelid);
}
// 添加 CUDesc 索引(CUDesc Index)的大小
Relation cudescIdx = try_relation_open(cudescRel->rd_rel->relcudescidx, AccessShareLock);
if (cudescIdx != NULL) {
totalsize += calculate_relation_size(&(cudescIdx->rd_node), cudescIdx->rd_backend, forknum);
// 仅当指定的叉号为主叉号时,累加 CUDesc 索引的大小到 totalsize 中
// 不计算 TOAST 表的大小,因为 CUDesc 索引没有 TOAST 子关系。
relation_close(cudescIdx, AccessShareLock); // 关闭 CUDesc 索引关系
}
relation_close(cudescRel, AccessShareLock); // 关闭 CUDesc 表关系
}
return totalsize; // 返回计算得到的列存储表总文件大小(totalsize)
}
calculate_relation_size 函数
这段代码的主要功能是计算数据库中指定关系(表或索引)的总文件大小 。它通过迭代处理每个文件段 来计算文件的大小,并将结果累加到 totalsize 变量中。这个功能对于数据库管理员和性能优化人员非常有用,可以帮助他们了解数据库中关系的实际存储需求,进行性能分析和优化决策。函数源码如下所示:(路径:src\common\backend\utils\adt\dbsize.cpp
)
cpp
int64 calculate_relation_size(RelFileNode* rfn, BackendId backend, ForkNumber forknum)
{
int64 totalsize = 0; // 初始化变量 totalsize,用于存储计算得到的关系(表或索引)的总文件大小
char* relationpath = NULL; // 声明变量 relationpath,用于存储关系的路径信息
char pathname[MAXPGPATH] = {'\0'}; // 声明数组 pathname,用于存储文件路径信息
unsigned int segcount = 0; // 初始化变量 segcount,用于迭代处理文件段
errno_t rc = EOK; // 初始化错误码变量 rc
relationpath = relpathbackend(*rfn, backend, forknum); // 获取关系的路径
// 迭代处理文件段
for (segcount = 0;; segcount++) {
struct stat fst; // 声明结构体 fst,用于存储文件状态信息
CHECK_FOR_INTERRUPTS(); // 检查是否有中断请求
// 构造文件的路径名
if (segcount == 0)
rc = snprintf_s(pathname, MAXPGPATH, MAXPGPATH - 1, "%s", relationpath);
else
rc = snprintf_s(pathname, MAXPGPATH, MAXPGPATH - 1, "%s.%u", relationpath, segcount);
securec_check_ss(rc, "\0", "\0");
// 获取文件状态信息
if (stat(pathname, &fst) < 0) {
if (errno == ENOENT) // 如果文件不存在,跳出循环
break;
else
ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathname)));
// 报告文件访问错误
}
totalsize += fst.st_size; // 累加文件大小到 totalsize 中
}
pfree_ext(relationpath); // 释放关系路径内存
return totalsize; // 返回计算得到的关系的总文件大小(totalsize)
}