ALTER TABLE(列存删除列)
- [ALTER TABLE ... SET TABLESPACE](#ALTER TABLE ... SET TABLESPACE)
- [ExecChangeTableSpaceForRowTable 函数](#ExecChangeTableSpaceForRowTable 函数)
-
- [ATExecSetTableSpace 函数](#ATExecSetTableSpace 函数)
- [ExecChangeTableSpaceForRowPartition 函数](#ExecChangeTableSpaceForRowPartition 函数)
-
- [ATExecSetTableSpaceForPartitionP3 函数](#ATExecSetTableSpaceForPartitionP3 函数)
- [ExecChangeTableSpaceForCStoreTable 函数](#ExecChangeTableSpaceForCStoreTable 函数)
-
- [ChangeTableSpaceForDeltaRelation 函数](#ChangeTableSpaceForDeltaRelation 函数)
- [CStoreSetTableSpaceForColumnData 函数](#CStoreSetTableSpaceForColumnData 函数)
- [ChangeTableSpaceForCudescRelation 函数](#ChangeTableSpaceForCudescRelation 函数)
- [ExecChangeTableSpaceForCStorePartition 函数](#ExecChangeTableSpaceForCStorePartition 函数)
-
- [ExecChangeTableSpaceForCStoreTableOrPartition 函数](#ExecChangeTableSpaceForCStoreTableOrPartition 函数)
声明 :本文的部分内容参考了他人的文章。在编写过程中,我们尊重他人的知识产权和学术成果,力求遵循合理使用原则,并在适用的情况下注明引用来源。
本文主要参考了 OpenGauss5.1.0 的开源代码和《OpenGauss数据库源码解析》一书
在OpenGauss 数据库中,ALTER TABLE ... SET TABLESPACE
命令允许将表和索引移动到新的表空间,这对于管理数据存储和性能优化至关重要。这条命令的主要语法是ALTER TABLE table_name SET TABLESPACE new_tablespace;
其中,table_name
是要移动的表的名称 ,new_tablespace
是目标表空间 的名称。
在执行这个命令之前,需要确保用户具有适当的数据库对象修改权限 。表空间分为系统表空间 和用户表空间 两种类型。系统表空间包含系统数据库和数据库模板的表空间,例如默认的 pg_default 表空间。用户表空间是用户可以创建的自定义表空间 ,用于存储用户数据。
移动表到新的表空间可能会涉及到重新组织表的物理存储结构 ,这取决于数据库引擎的具体实现和配置。特别是对于大型表或频繁访问的表,移动操作可能会导致一定程度的性能影响,因此在选择新表空间时需要考虑其存储容量 、I/O 性能等因素,以确保满足应用程序的要求。
在实际操作中,移动表到新表空间 可能会涉及到重建索引 或者其他元数据的更新,以适应新的存储位置和性能特征。这需要在执行操作前进行充分的计划和测试,以减少对数据库操作的影响,并确保操作的安全性和一致性。
ALTER TABLE ... SET TABLESPACE
ATController 函数在 OpenGauss 中扮演着管理和执行 ALTER TABLE
命令的关键角色。它被设计用来处理 ALTER TABLE
命令的多个阶段 ,确保命令的顺序执行和正确性。首先,它通过准备阶段收集和转换命令,建立工作队列以准备执行。接着,在系统目录更新阶段,ATController 确保系统目录和元数据的同步更新,为后续操作做好准备。最后,它调用表重写阶段来处理实际的表重组或移动操作,包括将表和索引重新分配到新的表空间 。
与 ALTER TABLE ... SET TABLESPACE
相关,ATController 在处理这类命令时,首先在准备阶段将其加入工作队列 。在系统目录更新阶段 ,它确保更新了系统目录,以反映表移动到新表空间的变化。最后,在表重写阶段 ,ATController 调用相关函数来实际执行表移动的操作,确保表和相关索引按照指定的新表空间进行重新分配和重建。
综上所述,ATController 是一个高度控制和管理 ALTER TABLE
命令执行过程的函数,通过明确定义的阶段处理,保证了命令的顺序执行和数据库的一致性。
其中,在函数 ATExecCmd 中(第二阶段),并不会对 SET TABLESPACE
的相关操作进行处理。而是全部由第三阶段来处理。Phase 3 执行的是将表和相关索引移动到新表空间的实际操作。这包括重新分配表所在的表空间和必要的物理重组织。
cpp
case AT_SetTableSpace: /* SET TABLESPACE */
/*
* Nothing to do here; Phase 3 does the work
*/
break;
在 ATRewriteTables 函数中,tab->rewrite 的值来决定是进行表的重写操作还是仅进行约束检查或表空间移动操作。更多详细的内容可以参考【OpenGauss源码学习 ------ (ALTER TABLE(Add Column))】
cpp
if (tab->rewrite > 0) {
// 构建临时关系并复制数据
Relation OldHeap;
Oid NewTableSpace;
OldHeap = heap_open(tab->relid, NoLock);
// 触发事件触发器,通知重写表操作即将发生
if (parsetree)
EventTriggerTableRewrite((Node *)parsetree,
tab->relid,
tab->rewrite);
// 检查是否为系统关系或者用作目录表,如果是则报错
if (IsSystemRelation(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite system relation \"%s\"", RelationGetRelationName(OldHeap))));
if (RelationIsUsedAsCatalogTable(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite table \"%s\" used as a catalog table", RelationGetRelationName(OldHeap))));
// 不允许重写其他会话的临时表
if (RELATION_IS_OTHER_TEMP(OldHeap))
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot rewrite temporary tables of other sessions")));
// 选择目标表空间,如果没有指定新表空间,则使用原始表的表空间
if (tab->newTableSpace)
NewTableSpace = tab->newTableSpace;
else
NewTableSpace = OldHeap->rd_rel->reltablespace;
heap_close(OldHeap, NoLock);
// 执行重写函数指针数组中对应位置的函数,进行表重写操作
ExecRewriteFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab, NewTableSpace, lockmode);
}
其中,上面代码使用了一个名为 ExecChangeTabspcFuncPtrArray 的二维数组,用于存储函数指针。
cpp
ExecChangeTabspcFuncPtr ExecChangeTabspcFuncPtrArray[2][2] = {
{ExecChangeTableSpaceForRowTable, ExecChangeTableSpaceForRowPartition},
{ExecChangeTableSpaceForCStoreTable, ExecChangeTableSpaceForCStorePartition}
};
通过 ExecChangeTabspcFuncPtrArray 数组,可以根据表的存储类型(行存储或列存储)和分区情况,快速选择并调用对应的表空间变更函数,实现表空间的移动或变更操作。
ExecChangeTableSpaceForRowTable 函数
ExecChangeTableSpaceForRowTable 函数实现了在 OpenGauss 数据库中用于修改行关系表(包括索引)的表空间的功能。该函数主要功能是在 OpenGauss 数据库中实现了修改行关系表(包括索引)的表空间的操作。函数接受两个参数 :tab 是用于存储修改表信息的结构体 ,lockmode 是在操作期间使用的锁模式 。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* @Description: change tablespace for row relation.
* PSort index is handled in this branch, because its oid is remembered here.
* @Param[IN] lockmode: lock mode used during changing tablespace.
* @Param[IN] tab: Alter Table Info
* @See also: the comments of function ExecChangeTableSpaceForRowPartition()
*/
static void ExecChangeTableSpaceForRowTable(AlteredTableInfo* tab, LOCKMODE lockmode)
{
// 调用ATExecSetTableSpace函数来修改表或索引的表空间
ATExecSetTableSpace(tab->relid, tab->newTableSpace, lockmode);
// 如果是索引类型或全局索引类型,则处理特殊的PSORT索引
if (tab->relkind == RELKIND_INDEX || tab->relkind == RELKIND_GLOBAL_INDEX) {
// 打开索引关系
Relation rel = index_open(tab->relid, lockmode);
// 如果索引关系的存储方式是PSORT,则调用PSortChangeTableSpace函数修改表空间
if (rel->rd_rel->relam == PSORT_AM_OID) {
PSortChangeTableSpace(rel->rd_rel->relcudescrelid, /* psort oid */
tab->newTableSpace,
lockmode);
}
// 关闭索引关系
index_close(rel, NoLock);
// 获取索引关联的堆表的OID
Oid heapId = IndexGetRelation(tab->relid, false);
// 根据堆表的OID获取关系对象
Relation userRel = RelationIdGetRelation(heapId);
// 更新PG对象的变更CSN
UpdatePgObjectChangecsn(heapId, userRel->rd_rel->relkind);
// 关闭关系对象
RelationClose(userRel);
}
}
ATExecSetTableSpace 函数
ATExecSetTableSpace 函数实现了在 OpenGauss 数据库中执行 ALTER TABLE SET TABLESPACE
操作的关键逻辑和步骤。首先,它通过打开目标表 并获取必要的锁 来准备执行操作。接着,它检查表是否是全局临时表 ,如果是则报错退出 。然后,通过比较当前表的表空间和新指定的表空间 ,决定是否需要进行实际的表空间修改 。如果需要修改,它会为表分配一个新的 relfilenode ,确保在新的表空间中分配适当的存储资源 。接着,它更新了 pg_class 系统表中关于表空间和 relfilenode 的信息 ,并记录修改日志以便后续审计和监控。最后,它递归处理表的关联 TOAST 表和索引 ,确保它们也被正确地迁移到新的表空间。这段代码保证了表空间变更操作的原子性和数据一致性,是数据库管理中重要的操作实现之一。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* 执行在OpenGauss数据库中执行 ALTER TABLE SET TABLESPACE 操作的函数。
*/
static void ATExecSetTableSpace(Oid tableOid, Oid newTableSpace, LOCKMODE lockmode)
{
Relation rel;
Oid reltoastrelid;
Oid reltoastidxid;
Oid newrelfilenode;
Relation pg_class;
HeapTuple tuple;
Form_pg_class rd_rel;
bool isbucket;
/* 要求 newTableSpace 必须有效 */
Assert(OidIsValid(newTableSpace));
/*
* 在递归到 TOAST 表或索引时需要锁定此处。
*/
// 打开指定 OID 的表,并获取相应的锁
rel = relation_open(tableOid, lockmode);
// 如果表是全局临时表,则抛出错误,不支持修改表空间操作
if (RELATION_IS_GLOBAL_TEMP(rel)) {
const char* objType = RelationIsIndex(rel) ? "index" : "table";
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("不支持在全局临时%s上执行设置表空间操作。", objType)));
}
/*
* 如果表空间没有变化,则无需执行任何操作。
*/
if (!NeedToSetTableSpace(rel, newTableSpace)) {
relation_close(rel, NoLock);
return;
}
// 获取表和其 TOAST 表、TOAST 索引的 OID
reltoastrelid = rel->rd_rel->reltoastrelid;
reltoastidxid = rel->rd_rel->reltoastidxid;
/* 获取表的可修改副本的 pg_class 行 */
// 打开 pg_class 系统表,并以 RowExclusiveLock 模式获取堆元组
pg_class = heap_open(RelationRelationId, RowExclusiveLock);
// 获取表的元组,并确保其有效性
tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tableOid));
if (!HeapTupleIsValid(tuple)) {
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("无法查找到表 %u 的缓存记录", tableOid)));
}
rd_rel = (Form_pg_class)GETSTRUCT(tuple);
/*
* Relfilenodes 在表空间之间不唯一,因此需要在新表空间中分配一个新的 relfilenode。
*/
if (rel->storage_type == HEAP_DISK) {
newrelfilenode = GetNewRelFileNode(newTableSpace, NULL, rel->rd_rel->relpersistence);
} else {
Assert(rel->storage_type == SEGMENT_PAGE);
bool newcbi = RelationIsCrossBucketIndex(rel);
isbucket = BUCKET_OID_IS_VALID(rel->rd_bucketoid) && !newcbi;
// 在新表空间中为分段存储表分配新的段号
newrelfilenode = seg_alloc_segment(newTableSpace, rel->rd_node.dbNode, isbucket, InvalidBlockNumber);
// 确保为跨存储桶索引创建正确的基础存储
rel->newcbi = newcbi;
}
// 调用 atexecset_table_space 函数来实际修改表的表空间和 relfilenode
atexecset_table_space(rel, newTableSpace, newrelfilenode);
// 记录日志,显示修改后的表空间和相关信息
elog(LOG, "行关系表: %s(%u) 表空间 %u/%u/%u => %u/%u/%u",
RelationGetRelationName(rel), RelationGetRelid(rel), rel->rd_node.spcNode, rel->rd_node.dbNode,
rel->rd_node.relNode, newTableSpace, rel->rd_node.dbNode, newrelfilenode);
/* 更新 pg_class 行 */
rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(newTableSpace);
rd_rel->relfilenode = newrelfilenode;
simple_heap_update(pg_class, &tuple->t_self, tuple);
CatalogUpdateIndexes(pg_class, tuple);
// 释放 pg_class 表中的元组资源
tableam_tops_free_tuple(tuple);
// 关闭 pg_class 表
heap_close(pg_class, RowExclusiveLock);
// 更新 PG 对象的变更 CSN
UpdatePgObjectChangecsn(tableOid, rel->rd_rel->relkind);
// 关闭表的关系对象
relation_close(rel, NoLock);
/* 确保表空间的修改对后续操作可见 */
CommandCounterIncrement();
/* 移动关联的 TOAST 表和/或索引 */
if (OidIsValid(reltoastrelid))
ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode);
if (OidIsValid(reltoastidxid))
ATExecSetTableSpace(reltoastidxid, newTableSpace, lockmode);
}
ExecChangeTableSpaceForRowPartition 函数
ExecChangeTableSpaceForRowPartition 函数的主要作用是为行分区(Row Partition)改变表空间。代码首先禁止对分区表设置表空间。接着,根据不同的分区类型进行处理:
- 对于行堆分区 和行索引分区 ,代码首先将数据复制到新的表空间,然后更新 pg_partition 表中的 reltablespace 和 relfilenode 字段,最后处理 toast 和 toast 索引(如有必要)。
- 对于 psort 索引分区 (列存储表的一种),代码首先更新 pg_partition 表中的 reltablespace 字段,然后将 psort 作为普通的列存储表进行处理。
此外,代码还处理了一种特殊情况,即 PSORT 索引类型,确保在改变表空间时正确处理此类型的索引。
ATExecSetTableSpaceForPartitionP3 函数
ATExecSetTableSpaceForPartitionP3 函数函数的主要功能是将一个数据分区移动到新的表空间。具体步骤如下:
- 打开关系并检查子分区: 首先打开指定的关系(表) ,并检查是否为子分区表。如果是,则抛出错误,因为当前不支持子分区表的表空间修改。
- 打开分区: 打开指定的分区 ,并检查旧表空间和新表空间是否相同,如果相同则无需执行任何操作。
- 检查新表空间有效性: 检查新表空间是否为 pg_global ,如果是则抛出错误,因为只有共享关系 可以放置在 pg_global 表空间中。
- 获取分区信息: 从缓存中获取分区信息 ,并确保分区的文件节点在新表空间中是唯一的。
- 分配新文件节点: 根据分区的存储类型 ,在新表空间中分配新的文件节点。
- 设置新表空间: 更新分区的表空间 和文件节点信息 ,并将这些更改写入系统目录。
- 日志记录和元组释放: 记录日志并释放相关的元组和关系。
- 处理相关的 toast 关系和索引: 如果分区有相关的 toast 关系或索引 ,也将它们移动到新表空间中。
通过这些步骤,函数确保分区在移动到新表空间后,所有相关的信息都被正确更新,且操作是原子性的,不会中断或留下不一致的状态。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* 目标 : 数据分区
* 简介 :
* 说明 : 将一个分区移动到新的表空间
* 注意 :
*/
static void ATExecSetTableSpaceForPartitionP3(Oid tableOid, Oid partOid, Oid newTableSpace, LOCKMODE lockmode)
{
Relation rel; // 定义关系变量
Relation partRel; // 定义分区关系变量
Partition part; // 定义分区变量
Oid oldTableSpace; // 定义旧表空间 OID
Oid reltoastrelid; // 定义 toast 关系 OID
Oid reltoastidxid; // 定义 toast 索引 OID
Oid newrelfilenode; // 定义新文件节点 OID
Relation pg_partition; // 定义分区表关系
HeapTuple tuple; // 定义元组变量
Form_pg_partition pd_part; // 定义分区表的结构体指针
bool isbucket; // 定义是否为桶存储的标志
bool newcbi = false; // 初始化新的跨桶索引标志
/*
* 需要在这里加锁以防止递归到 toast 表或索引
*/
rel = relation_open(tableOid, NoLock); // 打开关系
if (RelationIsSubPartitioned(rel)) { // 检查是否为子分区表
ereport(
ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
(errmsg("不支持的功能"),
errdetail("对于子分区表,尚不支持修改表空间。"),
errcause("该功能尚未实现。"), erraction("请使用其他操作。"))));
}
part = partitionOpen(rel, partOid, lockmode); // 打开分区
/*
* 如果表空间没有变化,则无需工作
*/
oldTableSpace = part->pd_part->reltablespace; // 获取旧表空间 OID
if (newTableSpace == oldTableSpace ||
(newTableSpace == u_sess->proc_cxt.MyDatabaseTableSpace && oldTableSpace == 0)) {
partitionClose(rel, part, NoLock); // 关闭分区
relation_close(rel, NoLock); // 关闭关系
return;
}
/* 不能将非共享关系移动到 pg_global */
if (newTableSpace == GLOBALTABLESPACE_OID)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("只有共享关系可以放置在 pg_global 表空间中")));
reltoastrelid = part->pd_part->reltoastrelid; // 获取 toast 关系 OID
reltoastidxid = part->pd_part->reltoastidxid; // 获取 toast 索引 OID
/* 获取可修改的分区表的副本 */
pg_partition = heap_open(PartitionRelationId, RowExclusiveLock); // 打开分区表
tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid)); // 从缓存中获取分区元组
if (!HeapTupleIsValid(tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("缓存查找失败,分区 %u", partOid)));
pd_part = (Form_pg_partition)GETSTRUCT(tuple); // 获取分区结构体指针
/*
* 文件节点在表空间中不唯一,因此我们需要在新表空间中分配一个新的节点
*/
if (RelationGetStorageType(rel) == (uint4)HEAP_DISK) {
newrelfilenode = GetNewRelFileNode(newTableSpace, NULL, rel->rd_rel->relpersistence); // 分配新文件节点
} else {
newcbi = RelationIsCrossBucketIndex(rel); // 判断是否为跨桶索引
isbucket = BUCKET_OID_IS_VALID(rel->rd_bucketoid) && !newcbi; // 判断是否为有效的桶 OID
Oid database_id = (ConvertToRelfilenodeTblspcOid(newTableSpace) == GLOBALTABLESPACE_OID) ?
InvalidOid : u_sess->proc_cxt.MyDatabaseId; // 获取数据库 ID
newrelfilenode = seg_alloc_segment(ConvertToRelfilenodeTblspcOid(newTableSpace),
database_id, isbucket, InvalidBlockNumber); // 分配新文件节点
}
partRel = partitionGetRelation(rel, part); // 获取分区关系
/* 确保我们为跨桶索引创建正确的底层存储 */
partRel->newcbi = newcbi;
atexecset_table_space(partRel, newTableSpace, newrelfilenode); // 设置表空间
elog(LOG,
"行分区: %s(%u) 表空间 %u/%u/%u => %u/%u/%u",
RelationGetRelationName(partRel),
RelationGetRelid(partRel),
partRel->rd_node.spcNode,
partRel->rd_node.dbNode,
partRel->rd_node.relNode,
newTableSpace,
partRel->rd_node.dbNode,
newrelfilenode); // 打日志
/* 更新分区表行 */
pd_part->reltablespace = ConvertToPgclassRelTablespaceOid(newTableSpace); // 更新表空间 OID
pd_part->relfilenode = newrelfilenode; // 更新文件节点 OID
simple_heap_update(pg_partition, &tuple->t_self, tuple); // 更新分区表
CatalogUpdateIndexes(pg_partition, tuple); // 更新索引
tableam_tops_free_tuple(tuple); // 释放元组
heap_close(pg_partition, RowExclusiveLock); // 关闭分区表
partitionClose(rel, part, NoLock); // 关闭分区
releaseDummyRelation(&partRel); // 释放虚拟关系
relation_close(rel, NoLock); // 关闭关系
/* 确保表空间更改可见 */
CommandCounterIncrement();
/* 也移动相关的 toast 关系和/或索引 */
if (OidIsValid(reltoastrelid))
ATExecSetTableSpace(reltoastrelid, newTableSpace, lockmode); // 设置 toast 表空间
if (OidIsValid(reltoastidxid))
ATExecSetTableSpace(reltoastidxid, newTableSpace, lockmode); // 设置 toast 索引表空间
}
ExecChangeTableSpaceForCStoreTable 函数
ExecChangeTableSpaceForCStoreTable 函数的主要功能是为列存储关系(Column Store Relation)更改表空间。具体步骤如下:
- 打开列存储关系: 首先打开指定的列存储关系 。如果目标表空间和当前表空间相同,则不需要进行任何操作。
- 锁定相关关系: 按照特定顺序锁定列存储关系 、Delta 关系 及其索引关系 、CU 描述符关系 及其索引关系,以确保数据一致性。
- 处理 Delta 关系和索引: 调用函数 ChangeTableSpaceForDeltaRelation 处理 Delta 关系和索引的表空间更改。
- 处理每个列的数据: 打开 pg_class 表并获取当前列存储关系的元组。调用 CStoreSetTableSpaceForColumnData 为列数据设置新的表空间 ,并更新 pg_class 表中的相应信息。
- 处理 CU 描述符和索引关系: 调用 ChangeTableSpaceForCudescRelation 处理 CU 描述符及其索引关系 的表空间更改。
- 关闭关系和释放资源: 关闭所有打开的关系并释放相关资源,确保所有更改在系统目录中可见。
通过这些步骤,函数确保列存储关系及其相关的 Delta 关系和 CU 描述符在移动到新表空间后,所有相关的信息都被正确更新,且操作是原子性的,不会中断或留下不一致的状态。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* @Description: 为列存储关系更改表空间。
* @Param[IN] lockmode: 在更改表空间期间使用的锁模式
* @Param[IN] tab: 更改表信息
* @See also:
*/
static void ExecChangeTableSpaceForCStoreTable(AlteredTableInfo* tab, LOCKMODE lockmode)
{
Relation colRel = NULL; // 定义列存储关系变量
Relation cudescRel = NULL; // 定义 CU 描述符关系变量
Relation cudescIdxRel = NULL; // 定义 CU 描述符索引关系变量
Oid cudescOid = InvalidOid; // 定义 CU 描述符 OID
Oid cudescIdxOid = InvalidOid; // 定义 CU 描述符索引 OID
Oid targetTableSpace = tab->newTableSpace; // 定义目标表空间 OID
Oid newrelfilenode = InvalidOid; // 定义新文件节点 OID
/* 这里可能打开一个堆关系或索引关系,因此调用 relation_open() */
colRel = relation_open(tab->relid, lockmode); // 打开列存储关系
/* 如果表空间没有变化,则无需工作 */
if (!NeedToSetTableSpace(colRel, targetTableSpace)) {
relation_close(colRel, NoLock); // 关闭列存储关系
return;
}
/* 锁的顺序:
* 1. 列存储关系
* 2. Delta 关系 [ Delta 索引关系 ]
* 3. CU 描述符关系 + CU 描述符索引关系
*/
if (OidIsValid(colRel->rd_rel->reldeltarelid)) {
LockRelationOid(colRel->rd_rel->reldeltarelid, lockmode); // 锁定 Delta 关系
}
cudescOid = colRel->rd_rel->relcudescrelid; // 获取 CU 描述符 OID
cudescRel = heap_open(cudescOid, lockmode); // 打开 CU 描述符关系
cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OID
cudescIdxRel = index_open(cudescIdxOid, lockmode); // 打开 CU 描述符索引关系
/* 1. 处理 Delta 和 Delta 索引关系 */
ChangeTableSpaceForDeltaRelation(colRel->rd_rel->reldeltarelid, targetTableSpace, lockmode);
/* 2. 处理每个列的数据 */
Relation pg_class = heap_open(RelationRelationId, RowExclusiveLock); // 打开 pg_class 表
/* 获取可修改的关系的 pg_class 行 */
HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(tab->relid)); // 从缓存中获取关系元组
if (!HeapTupleIsValid(tuple)) {
ereport(
ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("缓存查找失败,关系 %u", tab->relid)));
}
Form_pg_class rd_rel = (Form_pg_class)GETSTRUCT(tuple); // 获取关系的 pg_class 结构体
newrelfilenode = CStoreSetTableSpaceForColumnData(colRel, targetTableSpace); // 为列数据设置表空间
/* 更新 pg_class 行 */
rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace); // 更新表空间 OID
rd_rel->relfilenode = newrelfilenode; // 更新文件节点 OID
simple_heap_update(pg_class, &tuple->t_self, tuple); // 更新 pg_class 表
CatalogUpdateIndexes(pg_class, tuple); // 更新索引
tableam_tops_free_tuple(tuple); // 释放元组
heap_close(pg_class, RowExclusiveLock); // 关闭 pg_class 表
/* 确保表空间更改可见 */
CommandCounterIncrement();
/* 3. 处理 CU 描述符和索引关系 */
ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, lockmode);
index_close(cudescIdxRel, NoLock); // 关闭 CU 描述符索引关系
heap_close(cudescRel, NoLock); // 关闭 CU 描述符关系
relation_close(colRel, NoLock); // 关闭列存储关系
}
ChangeTableSpaceForDeltaRelation 函数
ChangeTableSpaceForDeltaRelation 函数的主要功能是为 Delta 关系更改表空间。Delta 关系通常用于存储列存储表中的增量数据。具体步骤如下:
- 检查 Delta 关系的有效性: 首先检查 deltaOid 是否有效,如果无效,则不进行任何操作。
- 转换表空间 Oid: 调用 ConvertToRelfilenodeTblspcOid 将目标表空间 Oid 转换为有效的表空间 Oid,并断言其有效性。这是因为 ATExecSetTableSpace 函数要求目标表空间必须有效。
- 锁定 Delta 关系: 使用指定的锁模式打开并锁定 Delta 关系,以确保在更改表空间过程中数据一致性。
- 更改 Delta 关系的表空间: 调用 ATExecSetTableSpace 函数为 Delta 关系更改表空间。
- 解锁 Delta 关系: 关闭 Delta 关系,但保持锁定状态直到事务提交,以确保更改的原子性 和一致性。
此函数确保 Delta 关系在更改表空间过程中保持一致性 和原子性 。如果 Delta 关系的 Oid 无效,则函数不会执行任何操作。函数逻辑简单明了,通过几个步骤保证 Delta 关系成功迁移到新的表空间。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* @Description: 为 Delta 关系更改表空间。
* @Param[IN] deltaOid: Delta 关系的 Oid
* @Param[IN] lockmode: 更改表空间期间使用的锁模式
* @Param[IN] targetTableSpace: 新的表空间
* @See also:
*/
static inline void ChangeTableSpaceForDeltaRelation(Oid deltaOid, Oid targetTableSpace, LOCKMODE lockmode)
{
if (OidIsValid(deltaOid)) { // 检查 Delta 关系的 Oid 是否有效
/* ATExecSetTableSpace() 要求 targetTableSpace 不是 InvalidOid */
targetTableSpace = ConvertToRelfilenodeTblspcOid(targetTableSpace); // 转换表空间 Oid
Assert(OidIsValid(targetTableSpace)); // 断言目标表空间 Oid 有效
/* 用锁模式锁定 Delta 关系 */
Relation deltaRel = heap_open(deltaOid, lockmode); // 打开 Delta 关系
/* 为 Delta 关系更改表空间 */
ATExecSetTableSpace(deltaOid, targetTableSpace, lockmode); // 调用函数更改表空间
/* 解除锁定直到提交 */
relation_close(deltaRel, NoLock); // 关闭 Delta 关系,不释放锁
/* 为 Delta 索引关系更改表空间(在此代码段未实现) */
}
}
CStoreSetTableSpaceForColumnData 函数
CStoreSetTableSpaceForColumnData 函数的主要功能是将列存储关系的所有列文件复制到新的表空间中,并返回在新表空间下的新文件节点。具体步骤如下:
- 分配新文件节点: 首先,在目标表空间中分配一个新的文件节点 。文件节点在表空间中不唯一,因此需要新分配。
- 创建 CU 复制关系: 基于新的文件节点和目标表空间 ,创建一个 CU (Column Unit )复制关系,用于后续的列数据复制操作。
- 遍历每一列: 获取列存储关系的所有列描述信息 ,逐列进行处理。
- 复制列数据: 对于未被删除的列 ,调用 CStoreCopyColumnData 函数,将列数据复制到 CU 复制关系中。
- 完成列数据复制: 调用 CStoreCopyColumnDataEnd 函数,完成所有列数据的复制过程。
- 销毁伪关系: 释放 CU 复制关系的相关资源。
- 返回新文件节点: 最后,返回在目标表空间中新分配的文件节点。
此函数通过上述步骤,确保列存储关系中的所有列数据被成功复制到新的表空间中,且操作过程中维护了数据的一致性和完整性。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.cpp
)
cpp
/*
* @Description: 将列存储关系的所有列文件复制到新的表空间。
* @Param[IN] colRel: 需要更改表空间的列存储关系
* @Param[IN] targetTableSpace: 新的目标表空间
* @Return: 新表空间下的新文件节点
* @See also:
*/
Oid CStoreSetTableSpaceForColumnData(Relation colRel, Oid targetTableSpace)
{
/*
* 文件节点在表空间中不唯一,因此我们需要在新表空间中分配一个新的节点。
*/
Oid newrelfilenode = GetNewRelFileNode(targetTableSpace, NULL, colRel->rd_rel->relpersistence);
/* 创建 CU 复制关系 */
RelFileNode CUReplicationFile = {
ConvertToRelfilenodeTblspcOid(targetTableSpace), colRel->rd_node.dbNode, newrelfilenode, InvalidBktId, 0
};
Relation CUReplicationRel = CreateCUReplicationRelation(
CUReplicationFile, colRel->rd_backend, colRel->rd_rel->relpersistence, RelationGetRelationName(colRel));
int nattrs = RelationGetDescr(colRel)->natts; // 获取列数
for (int i = 0; i < nattrs; ++i) { // 遍历每一列
Form_pg_attribute thisattr = &RelationGetDescr(colRel)->attrs[i];
if (!thisattr->attisdropped) { // 如果列未被删除
/* 为每列的数据更改表空间 */
CStoreCopyColumnData(CUReplicationRel, colRel, thisattr->attnum);
}
}
CStoreCopyColumnDataEnd(colRel, targetTableSpace, newrelfilenode); // 完成列数据复制
/* 销毁伪关系 */
FreeFakeRelcacheEntry(CUReplicationRel);
return newrelfilenode; // 返回新文件节点
}
ChangeTableSpaceForCudescRelation 函数
此函数 ChangeTableSpaceForCudescRelation 的主要功能是为 CU 描述符(Column Unit Description)及其索引关系更改表空间。具体步骤如下:
- 验证并转换目标表空间 Oid: 首先,调用 ConvertToRelfilenodeTblspcOid 函数,将目标表空间 Oid 转换为有效的表空间 Oid ,并使用 Assert 断言其有效性。ATExecSetTableSpace 函数要求目标表空间必须是有效的。
- 更改 CU 描述符关系的表空间: 断言 CU 描述符关系的 Oid 有效后,调用 ATExecSetTableSpace 函数,为 CU 描述符关系更改表空间。CU 描述符关系存储了列存储数据的元数据。
- 更改 CU 描述符索引关系的表空间: 断言 CU 描述符索引关系的 Oid 有效后,再次调用 ATExecSetTableSpace 函数,为 CU 描述符索引关系更改表空间。CU 描述符索引关系用于加速对 CU 描述符的查询。
此函数通过这些步骤,确保 CU 描述符及其索引关系在迁移到新的表空间后,所有相关的信息都被正确更新,且操作过程保持一致性和原子性。如果任何一个 Oid 无效,函数会通过断言报错,防止进一步执行。这种设计确保了数据迁移的安全性和可靠性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* @Description: 为 CU 描述符及其索引关系更改表空间。
* @Param[IN] cudescIdxOid: CU 描述符索引关系的 Oid
* @Param[IN] cudescOid: CU 描述符关系的 Oid
* @Param[IN] lockmode: 更改表空间期间使用的锁模式
* @Param[IN] targetTableSpace: 新的表空间
* @See also:
*/
static inline void ChangeTableSpaceForCudescRelation(
Oid cudescIdxOid, Oid cudescOid, Oid targetTableSpace, LOCKMODE lockmode)
{
/* ATExecSetTableSpace() 要求 targetTableSpace 是有效的 */
targetTableSpace = ConvertToRelfilenodeTblspcOid(targetTableSpace); // 转换表空间 Oid
Assert(OidIsValid(targetTableSpace)); // 断言目标表空间 Oid 有效
/* 为 CU 描述符关系更改表空间 */
Assert(OidIsValid(cudescOid)); // 断言 CU 描述符关系 Oid 有效
ATExecSetTableSpace(cudescOid, targetTableSpace, lockmode); // 调用函数更改 CU 描述符关系的表空间
/* 为 CU 描述符索引关系更改表空间 */
Assert(OidIsValid(cudescIdxOid)); // 断言 CU 描述符索引关系 Oid 有效
ATExecSetTableSpace(cudescIdxOid, targetTableSpace, lockmode); // 调用函数更改 CU 描述符索引关系的表空间
}
ExecChangeTableSpaceForCStorePartition 函数
ExecChangeTableSpaceForCStorePartition 函数的主要功能是为列存储分区(Column Store Partition)更改表空间。具体步骤如下:
- 初始化变量: 定义和初始化 用于存储父关系 、分区 、CU 描述符 及其索引关系的变量和 Oid。
- 禁止更改分区表的表空间: 调用 ForbidToChangeTableSpaceOfPartitionedTable 函数,确保不允许更改分区表的表空间。
- 锁定分区: 使用 AccessExclusiveLock 锁定分区,以确保数据一致性。
- 打开父关系和分区: 打开父关系 和分区 ,并获取分区关系 。如果表空间没有变化 ,则释放资源并返回。
- 锁定相关关系: 按照特定顺序锁定列存储关系 、Delta 关系 及其索引关系 、CU 描述符关系 及其索引关系。
- 处理 Delta 关系和索引: 调用 ChangeTableSpaceForDeltaRelation 函数,为 Delta 关系 和索引更改表空间。
- 处理每个列的数据: 打开 pg_partition 表,从缓存中获取分区元数据 ,调用 CStoreSetTableSpaceForColumnData 函数,为每列的数据更改表空间 ,并更新 pg_partition 表中的相应信息。
- 处理 CU 描述符和索引关系: 调用 ChangeTableSpaceForCudescRelation 函数,为 CU 描述符及其索引关系更改表空间。
- 释放资源: 关闭和释放所有打开的关系和资源,确保表空间更改操作的原子性和一致性。
通过这些步骤,函数确保列存储分区 及其相关的 Delta 关系和 CU 描述符在迁移到新的表空间后,所有相关的信息都被正确更新,且操作过程中维护了数据的一致性和完整性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp
)
cpp
/*
* @Description: 为列存储分区更改表空间。
* @Param[IN] lockmode: 在更改表空间期间使用的锁模式
* @Param[IN] tab: 更改表信息
* @See also:
*/
static void ExecChangeTableSpaceForCStorePartition(AlteredTableInfo* tab, LOCKMODE lockmode)
{
Relation parentRel = NULL; // 父关系
Partition partition = NULL; // 分区
Relation partitionRel = NULL; // 分区关系
Relation cudescRel = NULL; // CU 描述符关系
Relation cudescIdxRel = NULL; // CU 描述符索引关系
Oid partOid = tab->partid; // 分区 OID
Oid cudescOid = InvalidOid; // CU 描述符 OID
Oid cudescIdxOid = InvalidOid; // CU 描述符索引 OID
Oid targetTableSpace = tab->newTableSpace; // 目标表空间
Oid newrelfilenode = InvalidOid; // 新文件节点
// 禁止更改分区表的表空间
ForbidToChangeTableSpaceOfPartitionedTable(tab);
/* 输入的锁模式是分区表的锁模式,为 AccessShareLock。
* 这里应该使用分区的锁模式,即 AccessExclusiveLock。
* 另见 ATExecSetTableSpaceForPartitionP2()。
*/
const LOCKMODE partitionLock = AccessExclusiveLock;
/* 这里可能打开一个堆关系或索引关系,因此调用 relation_open() */
parentRel = relation_open(tab->relid, NoLock); // 打开父关系
partition = partitionOpen(parentRel, partOid, partitionLock); // 打开分区
partitionRel = partitionGetRelation(parentRel, partition); // 获取分区关系
/* 如果表空间没有变化,则无需工作 */
if (!NeedToSetTableSpace(partitionRel, targetTableSpace)) {
releaseDummyRelation(&partitionRel); // 释放分区关系
partitionClose(parentRel, partition, NoLock); // 关闭分区
relation_close(parentRel, NoLock); // 关闭父关系
return;
}
/* 锁的顺序:
* 1. 列存储关系
* 2. Delta 关系 [ Delta 索引关系 ]
* 3. CU 描述符关系 + CU 描述符索引关系
*/
if (OidIsValid(partitionRel->rd_rel->reldeltarelid)) {
LockRelationOid(partitionRel->rd_rel->reldeltarelid, partitionLock); // 锁定 Delta 关系
}
cudescOid = partitionRel->rd_rel->relcudescrelid; // 获取 CU 描述符 OID
cudescRel = heap_open(cudescOid, partitionLock); // 打开 CU 描述符关系
cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OID
cudescIdxRel = index_open(cudescIdxOid, partitionLock); // 打开 CU 描述符索引关系
/* 1. 处理 Delta 和 Delta 索引关系 */
ChangeTableSpaceForDeltaRelation(partitionRel->rd_rel->reldeltarelid, targetTableSpace, partitionLock);
/* 2. 处理每个列的数据 */
Relation pg_partition = heap_open(PartitionRelationId, RowExclusiveLock); // 打开 pg_partition 表
/* 获取可修改的分区表行 */
HeapTuple tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid)); // 从缓存中获取分区元组
if (!HeapTupleIsValid(tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("缓存查找失败,分区 %u", partOid)));
Form_pg_partition rd_rel = (Form_pg_partition)GETSTRUCT(tuple); // 获取分区表结构体指针
newrelfilenode = CStoreSetTableSpaceForColumnData(partitionRel, targetTableSpace); // 为列数据设置表空间
/* 更新 pg_partition 行 */
rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace); // 更新表空间 OID
rd_rel->relfilenode = newrelfilenode; // 更新文件节点 OID
simple_heap_update(pg_partition, &tuple->t_self, tuple); // 更新分区表
CatalogUpdateIndexes(pg_partition, tuple); // 更新索引
tableam_tops_free_tuple(tuple); // 释放元组
heap_close(pg_partition, RowExclusiveLock); // 关闭 pg_partition 表
/* 确保表空间更改可见 */
CommandCounterIncrement();
/* 3. 处理 CU 描述符和索引关系 */
ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, partitionLock);
index_close(cudescIdxRel, NoLock); // 关闭 CU 描述符索引关系
heap_close(cudescRel, NoLock); // 关闭 CU 描述符关系
releaseDummyRelation(&partitionRel); // 释放分区关系
partitionClose(parentRel, partition, NoLock); // 关闭分区
relation_close(parentRel, NoLock); // 关闭父关系
}
ExecChangeTableSpaceForCStoreTableOrPartition 函数
在 pg 中,分区表为继承表的形式,那么我们可不可以将以上列存分区表的处理函数修改为对 pg 分区表的处理呢?以下修改仅作为本人的一种想法,可供参考:
cpp
/*
* @Description: 为列存储表或列存储分区更改表空间。
* @Param[IN] tab: 更改表信息
* @Param[IN] lockmode: 更改表空间期间使用的锁模式
* @See also:
*/
static void ExecChangeTableSpaceForCStoreTableOrPartition(AlteredTableInfo* tab, LOCKMODE lockmode)
{
Relation parentRel = NULL; // 父关系
Relation colRel = NULL; // 列存储关系
Relation cudescRel = NULL; // CU 描述符关系
Relation cudescIdxRel = NULL; // CU 描述符索引关系
Oid cudescOid = InvalidOid; // CU 描述符 OID
Oid cudescIdxOid = InvalidOid; // CU 描述符索引 OID
Oid targetTableSpace = tab->newTableSpace; // 目标表空间
Oid newrelfilenode = InvalidOid; // 新文件节点
ForbidToChangeTableSpaceOfPartitionedTable(tab); // 禁止更改分区表的表空间
const LOCKMODE partitionLock = AccessExclusiveLock; // 分区锁模式
/* 打开父关系(表) */
parentRel = table_open(tab->relid, lockmode);
/* 检查是否为分区表 */
if (RelationGetPartitionKey(parentRel) != NULL) {
/* 遍历所有分区 */
List *partitions = RelationGetPartitionList(parentRel, lockmode);
ListCell *cell;
foreach(cell, partitions) {
Partition partition = (Partition) lfirst(cell);
Oid partOid = PartitionGetPartitionOid(partition);
/* 打开分区 */
colRel = table_open(partOid, partitionLock);
/* 如果表空间没有变化,则无需工作 */
if (!NeedToSetTableSpace(colRel, targetTableSpace)) {
table_close(colRel, NoLock); // 关闭分区关系
continue;
}
if (OidIsValid(colRel->rd_rel->reldeltarelid)) {
LockRelationOid(colRel->rd_rel->reldeltarelid, partitionLock); // 锁定 Delta 关系
}
cudescOid = colRel->rd_rel->relcudescrelid; // 获取 CU 描述符 OID
cudescRel = table_open(cudescOid, partitionLock); // 打开 CU 描述符关系
cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OID
cudescIdxRel = index_open(cudescIdxOid, partitionLock); // 打开 CU 描述符索引关系
/* 处理 Delta 和 Delta 索引关系 */
ChangeTableSpaceForDeltaRelation(colRel->rd_rel->reldeltarelid, targetTableSpace, partitionLock);
/* 处理每个列的数据 */
Relation pg_partition = table_open(PartitionRelationId, RowExclusiveLock); // 打开 pg_partition 表
/* 获取可修改的分区表行 */
HeapTuple tuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partOid)); // 从缓存中获取分区元组
if (!HeapTupleIsValid(tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("缓存查找失败,分区 %u", partOid)));
Form_pg_partition rd_rel = (Form_pg_partition)GETSTRUCT(tuple); // 获取 pg_partition 结构体
newrelfilenode = CStoreSetTableSpaceForColumnData(colRel, targetTableSpace); // 设置表空间
/* 更新 pg_partition 行 */
rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace); // 更新表空间 OID
rd_rel->relfilenode = newrelfilenode; // 更新文件节点 OID
simple_heap_update(pg_partition, &tuple->t_self, tuple); // 更新分区表元数据
CatalogUpdateIndexes(pg_partition, tuple); // 更新索引
tableam_tops_free_tuple(tuple); // 释放元组
table_close(pg_partition, RowExclusiveLock); // 关闭 pg_partition 表
/* 确保表空间更改可见 */
CommandCounterIncrement();
/* 处理 CU 描述符和索引关系 */
ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, partitionLock);
index_close(cudescIdxRel, NoLock); // 关闭 CU 描述符索引关系
table_close(cudescRel, NoLock); // 关闭 CU 描述符关系
table_close(colRel, NoLock); // 关闭分区关系
}
list_free_deep(partitions); // 释放分区列表
} else {
// 如果不是分区表,直接处理列存储关系
colRel = parentRel;
/* 如果表空间没有变化,则无需工作 */
if (!NeedToSetTableSpace(colRel, targetTableSpace)) {
table_close(parentRel, NoLock); // 关闭父关系
return;
}
if (OidIsValid(colRel->rd_rel->reldeltarelid)) {
LockRelationOid(colRel->rd_rel->reldeltarelid, partitionLock); // 锁定 Delta 关系
}
cudescOid = colRel->rd_rel->relcudescrelid; // 获取 CU 描述符 OID
cudescRel = table_open(cudescOid, partitionLock); // 打开 CU 描述符关系
cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OID
cudescIdxRel = index_open(cudescIdxOid, partitionLock); // 打开 CU 描述符索引关系
/* 处理 Delta 和 Delta 索引关系 */
ChangeTableSpaceForDeltaRelation(colRel->rd_rel->reldeltarelid, targetTableSpace, partitionLock);
/* 处理每个列的数据 */
Relation pg_class = table_open(RelationRelationId, RowExclusiveLock); // 打开 pg_class 表
/* 获取可修改的关系的 pg_class 行 */
HeapTuple tuple = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(colRel->rd_id)); // 从缓存中获取元组
if (!HeapTupleIsValid(tuple))
ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("缓存查找失败,关系 %u", colRel->rd_id)));
Form_pg_class rd_rel = (Form_pg_class)GETSTRUCT(tuple); // 获取 pg_class 结构体
newrelfilenode = CStoreSetTableSpaceForColumnData(colRel, targetTableSpace); // 设置表空间
/* 更新 pg_class 行 */
rd_rel->reltablespace = ConvertToPgclassRelTablespaceOid(targetTableSpace); // 更新表空间 OID
rd_rel->relfilenode = newrelfilenode; // 更新文件节点 OID
simple_heap_update(pg_class, &tuple->t_self, tuple); // 更新关系元数据
CatalogUpdateIndexes(pg_class, tuple); // 更新索引
tableam_tops_free_tuple(tuple); // 释放元组
table_close(pg_class, RowExclusiveLock); // 关闭 pg_class 表
/* 确保表空间更改可见 */
CommandCounterIncrement();
/* 处理 CU 描述符和索引关系 */
ChangeTableSpaceForCudescRelation(cudescIdxOid, cudescOid, targetTableSpace, partitionLock);
index_close(cudescIdxRel, NoLock); // 关闭 CU 描述符索引关系
table_close(cudescRel, NoLock); // 关闭 CU 描述符关系
}
table_close(parentRel, NoLock); // 关闭父关系
}