【OpenGauss源码学习 —— 列存储(获取表大小)】

获取表大小

  • 概述
    • [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 MapFSM ,用于跟踪空闲块的数据结构)、Visibility MapVM ,用于加速查询的数据结构)以及 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;
}

代码主要逻辑:

  1. 首先,代码定义了一个 int64 类型的变量 size ,用于存储计算得到的表的大小
  2. 代码通过检查表的类型 来决定如何计算表的大小 。如果表不是索引 ,会进入下面的逻辑分支:
    • 如果表是分布式表PGXC ),则调用 CalculateMotRelationSize 函数计算表的大小。
    • 如果表不是分布式表 ,会根据表是否是分区表执行不同的逻辑。如果不是分区表 ,则调用 calculate_table_file_size 函数计算表的文件大小 。如果是分区表 ,则迭代处理每个分区 ,计算每个分区的文件大小,并累加到 size 变量中 。如果表是时序存储表,则额外计算时序标签表的大小。
  3. 如果表是索引 ,代码会首先找到索引对应的基表 ,然后根据基表是否是列存储表 执行不同的逻辑:
    • 如果基表不是列存储表 ,则调用 calculate_table_file_size 函数计算索引文件的大小
    • 如果基表是列存储表 (同时要求索引使用 PSORT_AM ),则找到列存储索引对应的表,计算该表的文件大小。
  4. 最后,函数返回计算得到的表的大小

总的来说,这段代码提供了一个重要的功能,用于计算表的存储空间大小包括各种相关文件的大小。这对于数据库管理员和性能优化来说非常有用,可以帮助他们更好地管理数据库存储和性能。

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)
}
相关推荐
云和数据.ChenGuang2 分钟前
Django 应用安装脚本 – 如何将应用添加到 INSTALLED_APPS 设置中 原创
数据库·django·sqlite
woshilys30 分钟前
sql server 查询对象的修改时间
运维·数据库·sqlserver
Hacker_LaoYi31 分钟前
SQL注入的那些面试题总结
数据库·sql
建投数据1 小时前
建投数据与腾讯云数据库TDSQL完成产品兼容性互认证
数据库·腾讯云
Hacker_LaoYi2 小时前
【渗透技术总结】SQL手工注入总结
数据库·sql
岁月变迁呀3 小时前
Redis梳理
数据库·redis·缓存
独行soc3 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍06-基于子查询的SQL注入(Subquery-Based SQL Injection)
数据库·sql·安全·web安全·漏洞挖掘·hw
你的微笑,乱了夏天3 小时前
linux centos 7 安装 mongodb7
数据库·mongodb
工业甲酰苯胺3 小时前
分布式系统架构:服务容错
数据库·架构
独行soc4 小时前
#渗透测试#漏洞挖掘#红蓝攻防#护网#sql注入介绍08-基于时间延迟的SQL注入(Time-Based SQL Injection)
数据库·sql·安全·渗透测试·漏洞挖掘