【OpenGauss源码学习 —— (ALTER TABLE(调整表中特定列的统计目标))】

声明:本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss5.1.0 的开源代码和《OpenGauss数据库源码解析》一书

  ALTER TABLE ... ALTER COLUMN ... SET STATISTICS 命令用于调整数据库表中特定列的统计目标,以优化查询性能。通过设置列的统计信息,数据库查询优化器能够更准确地估算查询结果的选择性,从而生成更高效的查询计划。

ATPrepCmd 函数中的准备工作

AT_SetStatistics:设置列统计信息

case AT_SetStatistics: /* ALTER COLUMN SET STATISTICS */
    ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
    /* Performs own permission checks */
    ATPrepSetStatistics(rel);
    pass = AT_PASS_MISC;
    break;

详细解释:

  • AT_SetStatistics:这是一个 ALTER TABLE 子命令,用于设置列的统计信息
  • ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode)
    • 调用 ATSimpleRecursion 函数,递归处理当前表及其子表,传递 ALTER TABLE 命令。
    • wqueue:工作队列,用于存储处理过程中的临时信息。
    • rel当前正在处理的关系(表)。
    • cmdALTER TABLE 命令。
    • recurse是否递归处理子表
    • lockmode:锁模式。
  • ATPrepSetStatistics(rel)
    • 执行权限检查,确保当前用户有权限设置统计信息。
    • 准备设置统计信息的操作。
  • pass = AT_PASS_MISC
    • 设置当前操作的阶段为 AT_PASS_MISC,表示这是一个杂项操作阶段

AT_AddStatistics 和 AT_DeleteStatistics:添加和删除统计信息

case AT_AddStatistics:    /* ADD STATISTICS */
case AT_DeleteStatistics: /* DELETE STATISTICS */
    ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE);
    ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode);
    /* Performs own permission checks */
    ATPrepSetStatistics(rel);
    es_check_alter_table_statistics(rel, cmd);
    pass = AT_PASS_MISC;
    break;

详细解释:

  • AT_AddStatistics 和 AT_DeleteStatistics:这是两个 ALTER TABLE 子命令,用于添加和删除统计信息
  • ATSimplePermissions(rel, ATT_TABLE | ATT_FOREIGN_TABLE)
    • 调用 ATSimplePermissions 函数,检查当前用户是否有权限在普通表或外部表上添加或删除统计信息
    • rel:当前正在处理的关系(表)。
    • ATT_TABLE | ATT_FOREIGN_TABLE:指定要检查权限的对象类型,包括普通表外部表
  • ATSimpleRecursion(wqueue, rel, cmd, recurse, lockmode)
    • 调用 ATSimpleRecursion 函数,递归处理当前表及其子表,传递 ALTER TABLE 命令。
    • wqueue:工作队列,用于存储处理过程中的临时信息。
    • rel当前正在处理的关系(表)。
    • cmdALTER TABLE 命令。
    • recurse是否递归处理子表
    • lockmode:锁模式。
  • ATPrepSetStatistics(rel)
    • 执行权限检查,确保当前用户有权限设置统计信息。
    • 准备设置统计信息的操作。
  • es_check_alter_table_statistics(rel, cmd)
    • 额外的统计信息检查和准备工作,确保命令的正确性。
    • rel:当前正在处理的关系(表)。
    • cmdALTER TABLE 命令。
  • pass = AT_PASS_MISC
    • 设置当前操作的阶段为 AT_PASS_MISC,表示这是一个杂项操作阶段

ATSimpleRecursion 函数

  ATSimpleRecursion 函数的功能是进行简单的表递归,用于大多数 ALTER TABLE 操作。它会处理所有直接和间接的子表,并确保每个子表只被处理一次,即使它通过多个继承路径继承自原始表。该函数在递归过程中会将相关的操作命令传递给子表,并确保在修改之前检查表是否被占用。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/*
 * ATSimpleRecursion
 *
 * Simple table recursion sufficient for most ALTER TABLE operations.
 * All direct and indirect children are processed in an unspecified order.
 * Note that if a child inherits from the original table via multiple
 * inheritance paths, it will be visited just once.
 */
/* 
 * ATSimpleRecursion
 *
 * 该函数用于大多数ALTER TABLE操作的简单表递归。
 * 以未指定的顺序处理所有直接和间接的子表。
 * 注意,如果子表通过多个继承路径继承自原始表,它只会被访问一次。
 */
static void ATSimpleRecursion(List** wqueue, Relation rel, AlterTableCmd* cmd, bool recurse, LOCKMODE lockmode)
{
    /*
     * Propagate to children if desired.  Non-table relations never have
     * children, so no need to search in that case.
     */
    /*
     * 如果需要,则传递到子表。
     * 非表关系从不具有子表,因此无需在这种情况下搜索。
     */
    if (recurse && rel->rd_rel->relkind == RELKIND_RELATION) {
        Oid relid = RelationGetRelid(rel); // 获取关系ID
        ListCell* child = NULL; // 用于遍历子表列表的循环变量
        List* children = NIL; // 子表列表

        bool isDeltaStore = RelationIsCUFormat(rel); // 检查关系是否为列存储格式
#ifdef ENABLE_MULTIPLE_NODES
        isDeltaStore = g_instance.attr.attr_storage.enable_delta_store && isDeltaStore;
#endif
        if (isDeltaStore)

        /*
         * Under centrailzed mode, there may be unique index on delta table. When checking unique
         * constraint, unique index on delta will be used. So we ignore enable_delta_store here
         * and alter delta table at the same time.
         */
        /*
         * 在集中模式下,增量表上可能有唯一索引。
         * 检查唯一约束时,将使用增量表上的唯一索引。
         * 因此我们在这里忽略enable_delta_store,并同时更改增量表。
         */
            children = find_cstore_delta(rel, lockmode); // 查找列存储增量表的子表
        else
            children = find_all_inheritors(relid, lockmode, NULL); // 查找所有继承的子表

        /*
         * find_all_inheritors does the recursive search of the inheritance
         * hierarchy, so all we have to do is process all of the relids in the
         * list that it returns.
         */
        /*
         * find_all_inheritors执行继承层次结构的递归搜索,
         * 因此我们需要做的只是处理它返回的列表中的所有关系ID。
         */
        foreach (child, children) { // 遍历所有子表
            Oid childrelid = lfirst_oid(child); // 获取子表的关系ID
            Relation childrel;

            if (childrelid == relid)
                continue; // 跳过当前表本身
            /* find_all_inheritors already got lock */
            childrel = relation_open(childrelid, NoLock); // 打开子表关系,不获取锁
            CheckTableNotInUse(childrel, "ALTER TABLE"); // 检查子表是否正在使用
            ATPrepCmd(wqueue, childrel, cmd, false, true, lockmode, isDeltaStore); // 为子表准备ALTER命令
            relation_close(childrel, NoLock); // 关闭子表关系,不释放锁
        }
    }
}

es_check_alter_table_statistics 函数

  es_check_alter_table_statistics 函数的主要功能是ALTER TABLE 命令中的添加或删除统计信息操作进行预处理。具体而言,它识别并验证列名,将它们转换为位图集Bitmapset),以便在后续的统计信息操作中使用。通过这种预处理,确保用户指定的列名有效,并满足多列统计信息的要求(至少两列且不超过最大列数限制)。函数源码如下所示:(路径:src\common\backend\utils\adt\extended_statistics.cpp

/*
 * es_check_alter_table_statistics
 *     used by alter table commands to add/delete statistics
 *     recognize columns name and convert to a Bitmapset
 *
 * @param (in) rel:
 *     the relation
 * @param (in & out) cmd:
 *     the AlterTableCmd
 */
/*
 * es_check_alter_table_statistics
 *     由ALTER TABLE命令用于添加/删除统计信息
 *     识别列名并转换为Bitmapset
 *
 * @param (in) rel:
 *     关系(表)
 * @param (in & out) cmd:
 *     AlterTableCmd结构
 */
void es_check_alter_table_statistics(Relation rel, AlterTableCmd* cmd)
{
    Assert(IsA(cmd->def, List)); // 确认cmd->def是一个List类型
    List* new_def = NIL; // 初始化新的定义列表

    ListCell* lc1 = NULL; // 定义一个循环变量,用于遍历cmd->def列表
    foreach (lc1, (List*)cmd->def) { // 遍历cmd->def列表
        Node* columns = (Node*)lfirst(lc1); // 获取当前列表元素
        Assert(IsA(columns, List)); // 确认当前元素是一个List类型
        Bitmapset* bms_attnums = NULL; // 初始化一个Bitmapset用于存储列的attnum

        ListCell* lc2 = NULL; // 定义一个循环变量,用于遍历列名列表
        foreach (lc2, (List*)columns) { // 遍历列名列表
            Node* col_name = (Node*)lfirst(lc2); // 获取当前列名
            Assert(IsA(col_name, String)); // 确认列名是一个String类型
            char* col_name_str = strVal(col_name); // 将列名转换为字符串
            int col_attnum = attnameAttNum(rel, col_name_str, false); // 获取列的attnum
            if (InvalidAttrNumber != col_attnum) { // 如果attnum有效
                bms_attnums = bms_add_member(bms_attnums, col_attnum); // 将attnum添加到Bitmapset中
                elog(ES_LOGLEVEL, "Define multi column stats, colname[%s] attnum[%d]", col_name_str, col_attnum); // 日志记录添加的列名和attnum
            } else {
                es_error_column_not_exist(RelationGetRelationName(rel), col_name_str); // 如果attnum无效,报错列不存在
            }
        }

        int column_size = bms_num_members(bms_attnums); // 获取Bitmapset中的成员数量
        if (column_size < 2) { // 如果成员数量小于2
            ereport(ERROR,
                (errmodule(MOD_OPT),
                    errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("Multi-column statistic needs at least two columns."))); // 报错,多列统计需要至少两列
        }

        if (column_size > ES_MAX_COLUMN_SIZE) { // 如果成员数量大于最大列数
            ereport(ERROR,
                (errmodule(MOD_OPT),
                    errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("Multi-column statistic supports at most %d columns.", ES_MAX_COLUMN_SIZE))); // 报错,多列统计最多支持指定数量的列
        }

        new_def = lappend(new_def, bms_attnums); // 将Bitmapset添加到新的定义列表中
    }

    cmd->def = (Node*)new_def; // 更新cmd->def为新的定义列表
}

ATPrepSetStatistics 函数

  ATPrepSetStatistics 函数的主要功能是为执行 ALTER TABLE ALTER COLUMN SET STATISTICS 操作做准备,具体包括对目标关系索引等)进行必要的权限检查。该函数确保了当前用户有足够的权限执行设置统计信息的操作,并且目标关系是允许进行此操作的类型(例如,物化视图索引等)。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/*
 * ATPrepSetStatistics
 *
 * 该函数用于在执行ALTER TABLE ALTER COLUMN SET STATISTICS操作之前进行必要的权限检查。
 */
static void ATPrepSetStatistics(Relation rel)
{
    /*
     * We do our own permission checking because (a) we want to allow SET
     * STATISTICS on indexes (for expressional index columns), and (b) we want
     * to allow SET STATISTICS on system catalogs without requiring
     * allowSystemTableMods to be turned on.
     */
    /*
     * 我们进行自定义的权限检查,因为
     * (a) 我们希望允许在索引上设置统计信息(针对表达式索引列),并且
     * (b) 我们希望允许在系统目录上设置统计信息,而不需要开启allowSystemTableMods选项。
     */
    if (rel->rd_rel->relkind != RELKIND_RELATION && !RelationIsIndex(rel) && rel->rd_rel->relkind != RELKIND_STREAM &&
        rel->rd_rel->relkind != RELKIND_FOREIGN_TABLE && rel->rd_rel->relkind != RELKIND_MATVIEW)
        /*
         * 检查当前关系是否是表、索引、流、外部表或物化视图。
         * 如果不是,则抛出错误,提示对象类型不正确。
         */
        ereport(ERROR,
                (errcode(ERRCODE_WRONG_OBJECT_TYPE),
                errmsg("\"%s\" is not a table, materialized view, index, or foreign table", RelationGetRelationName(rel))));

    /* Permissions checks */
    /* 权限检查 */
    AclResult aclresult = pg_class_aclcheck(RelationGetRelid(rel), GetUserId(), ACL_ALTER);
    /* 检查当前用户是否具有ALTER权限 */
    if (aclresult != ACLCHECK_OK && !pg_class_ownercheck(RelationGetRelid(rel), GetUserId())) {
        /* 如果没有ALTER权限,并且不是表的所有者,则抛出权限错误 */
        aclcheck_error(ACLCHECK_NO_PRIV, ACL_KIND_CLASS, RelationGetRelationName(rel));
    }
}

ATExecSetStatistics 函数

  在 OpenGauss 中,ATExecSetStatistics 函数负责执行这一操作,确保新统计信息的正确应用和存储,从而提高数据库的整体性能和效率。函数ATExecSetStatistics 实现了设置表列统计目标值的功能。根据传入的新统计目标值和附加属性,限制和调整目标值的范围。然后在属性关系表中找到对应列的元组,更新其统计目标字段值,并确保系统目录中的索引保持最新。最后返回修改后列的对象地址。其主要功能包括:

  1. 参数初始化和断言验证
  2. 目标值范围限制
  3. 打开属性关系表和获取元组
  4. 元组有效性检查
  5. 修改属性元组
  6. 设置新的统计目标值
  7. 更新系统目录
  8. 设置返回的对象地址
  9. 释放资源
  10. 关闭属性关系表
  11. 返回结果

  详细代码描述如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/*
 * Return value is the address of the modified column
 */
static ObjectAddress ATExecSetStatistics(
    Relation rel, const char* colName, Node* newValue, AlterTableStatProperty additional_property, LOCKMODE lockmode)
{
    int newtarget;                       // 存储新的统计目标值
    Relation attrelation;                // 属性关系表的描述符
    HeapTuple tuple;                     // 属性元组
    Form_pg_attribute attrtuple;         // 属性元组的结构体指针
    AttrNumber  attnum;                  // 属性号
    ObjectAddress address;               // 返回的对象地址结构体

    Assert(IsA(newValue, Integer));     // 断言新值是一个整数类型
    newtarget = intVal(newValue);       // 获取整数类型的新值

    /*
     * Limit target to a sane range
     */
    if (additional_property == AT_CMD_WithoutPercent) {
        if (newtarget < -1) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("statistics target %d is too low", newtarget)));
        } else if (newtarget > 10000) {
            newtarget = 10000;
            ereport(WARNING,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("lowering statistics target to %d", newtarget)));
        }
    } else {
        // Example:additional_property == AT_CMD_WithPercent
        if (newtarget < 0 || newtarget > 100) {
            ereport(ERROR,
                (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
                    errmsg("statistics percent valid value is between 0 and 100")));
        }

        newtarget = -1 * newtarget - 1;  // 计算百分比情况下的新统计目标值
    }

    attrelation = heap_open(AttributeRelationId, RowExclusiveLock);  // 打开属性关系表获取锁

    tuple = SearchSysCacheCopyAttName(RelationGetRelid(rel), colName);  // 在系统缓存中搜索指定列的元组副本

    if (!HeapTupleIsValid(tuple))
        ereport(ERROR,
            (errcode(ERRCODE_UNDEFINED_COLUMN),
                errmsg("column \"%s\" of relation \"%s\" does not exist", colName, RelationGetRelationName(rel))));
    attrtuple = (Form_pg_attribute)GETSTRUCT(tuple);  // 获取属性元组的结构体指针

    attnum = attrtuple->attnum;  // 获取属性号
    if (attnum <= 0)
        ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("cannot alter system column \"%s\"", colName)));

    attrtuple->attstattarget = newtarget;  // 设置属性元组的统计目标字段值

    simple_heap_update(attrelation, &tuple->t_self, tuple);  // 更新属性关系表中的元组

    /* keep system catalog indexes current */
    CatalogUpdateIndexes(attrelation, tuple);  // 更新系统目录中的索引信息
    ObjectAddressSubSet(address, RelationRelationId,
                        RelationGetRelid(rel), attnum);  // 设置返回的对象地址信息

    tableam_tops_free_tuple(tuple);  // 释放元组占用的内存

    heap_close(attrelation, RowExclusiveLock);  // 关闭属性关系表

    return address;  // 返回修改后的列的对象地址
}

SearchSysCacheCopyAttName 函数

  函数 SearchSysCacheCopyAttName 的作用是在系统缓存中根据给定的关系 ID列名查找对应的列元组,并返回其副本。如果找不到指定的列元组,返回一个无效的 HeapTuple。详细代码描述如下所示:(路径:src\common\backend\utils\cache\syscache.cpp

/*
 * SearchSysCacheCopyAttName
 *
 * 在 SearchSysCacheCopy 的基础上,提供对已删除列(attisdropped)的支持版本。
 * 该函数根据关系ID和列名在系统缓存中查找对应的列元组,并返回其副本。
 * 如果找不到对应列元组,返回一个无效的 HeapTuple。
 */
HeapTuple SearchSysCacheCopyAttName(Oid relid, const char* attname)
{
    HeapTuple tuple, newtuple;

    // 调用 SearchSysCacheAttName 查找指定表和列名的列元组
    tuple = SearchSysCacheAttName(relid, attname);

    // 如果找不到有效的列元组,直接返回该无效的元组
    if (!HeapTupleIsValid(tuple)) {
        return tuple;
    }

    // 复制找到的列元组,得到一个新的 HeapTuple
    newtuple = heap_copytuple(tuple);

    // 释放原始的列元组缓存
    ReleaseSysCache(tuple);

    // 返回新的列元组副本
    return newtuple;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值