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:当前正在处理的关系(表)。
- cmd:
ALTER TABLE
命令。 - recurse:是否递归处理子表。
- lockmode:锁模式。
- 调用 ATSimpleRecursion 函数,递归处理当前表及其子表,传递
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:当前正在处理的关系(表)。
- cmd:
ALTER TABLE
命令。 - recurse:是否递归处理子表。
- lockmode:锁模式。
- 调用 ATSimpleRecursion 函数,递归处理当前表及其子表,传递
ATPrepSetStatistics(rel)
:- 执行权限检查,确保当前用户有权限设置统计信息。
- 准备设置统计信息的操作。
es_check_alter_table_statistics(rel, cmd)
:- 额外的统计信息检查和准备工作,确保命令的正确性。
- rel:当前正在处理的关系(表)。
- cmd:
ALTER 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 实现了设置表列统计目标值的功能。根据传入的新统计目标值和附加属性,限制和调整目标值的范围。然后在属性关系表中找到对应列的元组,更新其统计目标字段值,并确保系统目录中的索引保持最新。最后返回修改后列的对象地址。其主要功能包括:
- 参数初始化和断言验证
- 目标值范围限制
- 打开属性关系表和获取元组
- 元组有效性检查
- 修改属性元组
- 设置新的统计目标值
- 更新系统目录
- 设置返回的对象地址
- 释放资源
- 关闭属性关系表
- 返回结果
详细代码描述如下所示:(路径: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;
}