【OpenGauss源码学习 —— (ALTER TABLE(修改表空间))】

ALTER TABLE(列存删除列)

  • ALTER TABLE ... SET TABLESPACE
  • ExecChangeTableSpaceForRowTable 函数
    • ATExecSetTableSpace 函数
  • ExecChangeTableSpaceForRowPartition 函数
    • ATExecSetTableSpaceForPartitionP3 函数
  • ExecChangeTableSpaceForCStoreTable 函数
    • ChangeTableSpaceForDeltaRelation 函数
    • CStoreSetTableSpaceForColumnData 函数
    • ChangeTableSpaceForCudescRelation 函数
  • ExecChangeTableSpaceForCStorePartition 函数
    • 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 执行的是将表和相关索引移动到新表空间的实际操作。这包括重新分配表所在的表空间和必要的物理重组织。

case AT_SetTableSpace:	/* SET TABLESPACE *//** Nothing to do here; Phase 3 does the work*/break;

  在 ATRewriteTables 函数中,tab->rewrite 的值来决定是进行表的重写操作还是仅进行约束检查或表空间移动操作。更多详细的内容可以参考【OpenGauss源码学习 —— (ALTER TABLE(Add Column))】

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;elseNewTableSpace = OldHeap->rd_rel->reltablespace;heap_close(OldHeap, NoLock);// 执行重写函数指针数组中对应位置的函数,进行表重写操作ExecRewriteFuncPtrArray[rel_format_idx][idxPartitionedOrNot](tab, NewTableSpace, lockmode);
}

  其中,上面代码使用了一个名为 ExecChangeTabspcFuncPtrArray 的二维数组,用于存储函数指针

ExecChangeTabspcFuncPtr ExecChangeTabspcFuncPtrArray[2][2] = {{ExecChangeTableSpaceForRowTable, ExecChangeTableSpaceForRowPartition},{ExecChangeTableSpaceForCStoreTable, ExecChangeTableSpaceForCStorePartition}
};

  通过 ExecChangeTabspcFuncPtrArray 数组,可以根据表的存储类型(行存储或列存储)和分区情况,快速选择并调用对应的表空间变更函数,实现表空间的移动或变更操作。

ExecChangeTableSpaceForRowTable 函数

  ExecChangeTableSpaceForRowTable 函数实现了在 OpenGauss 数据库中用于修改行关系表(包括索引)的表空间的功能。该函数主要功能是在 OpenGauss 数据库中实现了修改行关系表(包括索引)的表空间的操作。函数接受两个参数tab 是用于存储修改表信息的结构体lockmode 是在操作期间使用的锁模式。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.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);// 获取索引关联的堆表的OIDOid heapId = IndexGetRelation(tab->relid, false);// 根据堆表的OID获取关系对象Relation userRel = RelationIdGetRelation(heapId);// 更新PG对象的变更CSNUpdatePgObjectChangecsn(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

/** 执行在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 索引的 OIDreltoastrelid = 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 函数来实际修改表的表空间和 relfilenodeatexecset_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 对象的变更 CSNUpdatePgObjectChangecsn(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)改变表空间。代码首先禁止对分区表设置表空间。接着,根据不同的分区类型进行处理:

  1. 对于行堆分区行索引分区,代码首先将数据复制到新的表空间,然后更新 pg_partition 表中的 reltablespacerelfilenode 字段,最后处理 toasttoast 索引(如有必要)。
  2. 对于 psort 索引分区(列存储表的一种),代码首先更新 pg_partition 表中的 reltablespace 字段,然后将 psort 作为普通的列存储表进行处理。

  此外,代码还处理了一种特殊情况,即 PSORT 索引类型,确保在改变表空间时正确处理此类型的索引。

ATExecSetTableSpaceForPartitionP3 函数

  ATExecSetTableSpaceForPartitionP3 函数函数的主要功能是将一个数据分区移动到新的表空间。具体步骤如下:

  1. 打开关系并检查子分区: 首先打开指定的关系(表),并检查是否为子分区表。如果是,则抛出错误,因为当前不支持子分区表的表空间修改。
  2. 打开分区: 打开指定的分区,并检查旧表空间和新表空间是否相同,如果相同则无需执行任何操作。
  3. 检查新表空间有效性: 检查新表空间是否为 pg_global,如果是则抛出错误,因为只有共享关系可以放置在 pg_global 表空间中。
  4. 获取分区信息: 从缓存中获取分区信息,并确保分区的文件节点在新表空间中是唯一的。
  5. 分配新文件节点: 根据分区的存储类型,在新表空间中分配新的文件节点
  6. 设置新表空间: 更新分区的表空间文件节点信息,并将这些更改写入系统目录
  7. 日志记录和元组释放: 记录日志并释放相关的元组和关系
  8. 处理相关的 toast 关系和索引: 如果分区有相关的 toast 关系或索引,也将它们移动到新表空间中

  通过这些步骤,函数确保分区在移动到新表空间后,所有相关的信息都被正确更新,且操作是原子性的,不会中断或留下不一致的状态。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.cpp

/** 目标         : 数据分区* 简介         :* 说明         : 将一个分区移动到新的表空间* 注意         :*/
static void ATExecSetTableSpaceForPartitionP3(Oid tableOid, Oid partOid, Oid newTableSpace, LOCKMODE lockmode)
{Relation rel; // 定义关系变量Relation partRel; // 定义分区关系变量Partition part; // 定义分区变量Oid oldTableSpace; // 定义旧表空间 OIDOid reltoastrelid; // 定义 toast 关系 OIDOid reltoastidxid; // 定义 toast 索引 OIDOid newrelfilenode; // 定义新文件节点 OIDRelation 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; // 获取旧表空间 OIDif (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 关系 OIDreltoastidxid = 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; // 判断是否为有效的桶 OIDOid database_id = (ConvertToRelfilenodeTblspcOid(newTableSpace) == GLOBALTABLESPACE_OID) ?InvalidOid : u_sess->proc_cxt.MyDatabaseId; // 获取数据库 IDnewrelfilenode = 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); // 更新表空间 OIDpd_part->relfilenode = newrelfilenode; // 更新文件节点 OIDsimple_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)更改表空间。具体步骤如下:

  1. 打开列存储关系: 首先打开指定的列存储关系。如果目标表空间和当前表空间相同,则不需要进行任何操作。
  2. 锁定相关关系: 按照特定顺序锁定列存储关系Delta 关系及其索引关系CU 描述符关系及其索引关系,以确保数据一致性。
  3. 处理 Delta 关系和索引: 调用函数 ChangeTableSpaceForDeltaRelation 处理 Delta 关系和索引的表空间更改。
  4. 处理每个列的数据: 打开 pg_class 表并获取当前列存储关系的元组。调用 CStoreSetTableSpaceForColumnData 为列数据设置新的表空间,并更新 pg_class 表中的相应信息
  5. 处理 CU 描述符和索引关系: 调用 ChangeTableSpaceForCudescRelation 处理 CU 描述符及其索引关系表空间更改
  6. 关闭关系和释放资源: 关闭所有打开的关系并释放相关资源,确保所有更改在系统目录中可见。

  通过这些步骤,函数确保列存储关系及其相关的 Delta 关系和 CU 描述符在移动到新表空间后,所有相关的信息都被正确更新,且操作是原子性的,不会中断或留下不一致的状态。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.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 描述符 OIDOid cudescIdxOid = InvalidOid; // 定义 CU 描述符索引 OIDOid targetTableSpace = tab->newTableSpace; // 定义目标表空间 OIDOid 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 描述符 OIDcudescRel = heap_open(cudescOid, lockmode); // 打开 CU 描述符关系cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OIDcudescIdxRel = 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); // 更新表空间 OIDrd_rel->relfilenode = newrelfilenode; // 更新文件节点 OIDsimple_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 关系通常用于存储列存储表中的增量数据。具体步骤如下:

  1. 检查 Delta 关系的有效性: 首先检查 deltaOid 是否有效,如果无效,则不进行任何操作。
  2. 转换表空间 Oid: 调用 ConvertToRelfilenodeTblspcOid 将目标表空间 Oid 转换为有效的表空间 Oid,并断言其有效性。这是因为 ATExecSetTableSpace 函数要求目标表空间必须有效。
  3. 锁定 Delta 关系: 使用指定的锁模式打开并锁定 Delta 关系,以确保在更改表空间过程中数据一致性。
  4. 更改 Delta 关系的表空间: 调用 ATExecSetTableSpace 函数为 Delta 关系更改表空间。
  5. 解锁 Delta 关系: 关闭 Delta 关系,但保持锁定状态直到事务提交,以确保更改的原子性一致性

  此函数确保 Delta 关系在更改表空间过程中保持一致性原子性。如果 Delta 关系的 Oid 无效,则函数不会执行任何操作。函数逻辑简单明了,通过几个步骤保证 Delta 关系成功迁移到新的表空间。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.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); // 转换表空间 OidAssert(OidIsValid(targetTableSpace)); // 断言目标表空间 Oid 有效/* 用锁模式锁定 Delta 关系 */Relation deltaRel = heap_open(deltaOid, lockmode); // 打开 Delta 关系/* 为 Delta 关系更改表空间 */ATExecSetTableSpace(deltaOid, targetTableSpace, lockmode); // 调用函数更改表空间/* 解除锁定直到提交 */relation_close(deltaRel, NoLock); // 关闭 Delta 关系,不释放锁/* 为 Delta 索引关系更改表空间(在此代码段未实现) */}
}

CStoreSetTableSpaceForColumnData 函数

  CStoreSetTableSpaceForColumnData 函数的主要功能是将列存储关系的所有列文件复制到新的表空间中,并返回在新表空间下的新文件节点。具体步骤如下:

  1. 分配新文件节点: 首先,在目标表空间中分配一个新的文件节点。文件节点在表空间中不唯一,因此需要新分配。
  2. 创建 CU 复制关系: 基于新的文件节点和目标表空间,创建一个 CUColumn Unit)复制关系,用于后续的列数据复制操作。
  3. 遍历每一列: 获取列存储关系的所有列描述信息逐列进行处理。
  4. 复制列数据: 对于未被删除的列,调用 CStoreCopyColumnData 函数,将列数据复制到 CU 复制关系中
  5. 完成列数据复制: 调用 CStoreCopyColumnDataEnd 函数,完成所有列数据的复制过程。
  6. 销毁伪关系: 释放 CU 复制关系的相关资源。
  7. 返回新文件节点: 最后,返回在目标表空间中新分配的文件节点

  此函数通过上述步骤,确保列存储关系中的所有列数据被成功复制到新的表空间中,且操作过程中维护了数据的一致性和完整性。函数源码如下所示:(路径:src\gausskernel\storage\cstore\cstore_rewrite.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)及其索引关系更改表空间。具体步骤如下:

  1. 验证并转换目标表空间 Oid: 首先,调用 ConvertToRelfilenodeTblspcOid 函数,将目标表空间 Oid 转换为有效的表空间 Oid,并使用 Assert 断言其有效性。ATExecSetTableSpace 函数要求目标表空间必须是有效的。
  2. 更改 CU 描述符关系的表空间: 断言 CU 描述符关系的 Oid 有效后,调用 ATExecSetTableSpace 函数,为 CU 描述符关系更改表空间。CU 描述符关系存储了列存储数据的元数据。
  3. 更改 CU 描述符索引关系的表空间: 断言 CU 描述符索引关系的 Oid 有效后,再次调用 ATExecSetTableSpace 函数,为 CU 描述符索引关系更改表空间。CU 描述符索引关系用于加速对 CU 描述符的查询。

  此函数通过这些步骤,确保 CU 描述符及其索引关系在迁移到新的表空间后,所有相关的信息都被正确更新,且操作过程保持一致性和原子性。如果任何一个 Oid 无效,函数会通过断言报错,防止进一步执行。这种设计确保了数据迁移的安全性和可靠性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.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); // 转换表空间 OidAssert(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)更改表空间。具体步骤如下:

  1. 初始化变量: 定义和初始化用于存储父关系分区CU 描述符及其索引关系的变量和 Oid
  2. 禁止更改分区表的表空间: 调用 ForbidToChangeTableSpaceOfPartitionedTable 函数,确保不允许更改分区表的表空间
  3. 锁定分区: 使用 AccessExclusiveLock 锁定分区,以确保数据一致性。
  4. 打开父关系和分区: 打开父关系分区,并获取分区关系。如果表空间没有变化,则释放资源并返回
  5. 锁定相关关系: 按照特定顺序锁定列存储关系Delta 关系及其索引关系CU 描述符关系及其索引关系
  6. 处理 Delta 关系和索引: 调用 ChangeTableSpaceForDeltaRelation 函数,为 Delta 关系索引更改表空间。
  7. 处理每个列的数据: 打开 pg_partition 表,从缓存中获取分区元数据,调用 CStoreSetTableSpaceForColumnData 函数,为每列的数据更改表空间,并更新 pg_partition 表中的相应信息。
  8. 处理 CU 描述符和索引关系: 调用 ChangeTableSpaceForCudescRelation 函数,为 CU 描述符及其索引关系更改表空间
  9. 释放资源: 关闭和释放所有打开的关系和资源,确保表空间更改操作的原子性和一致性。

  通过这些步骤,函数确保列存储分区及其相关的 Delta 关系和 CU 描述符在迁移到新的表空间后,所有相关的信息都被正确更新,且操作过程中维护了数据的一致性和完整性。函数源码如下所示:(路径:src\gausskernel\optimizer\commands\tablecmds.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; // 分区 OIDOid cudescOid = InvalidOid; // CU 描述符 OIDOid cudescIdxOid = InvalidOid; // CU 描述符索引 OIDOid 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 描述符 OIDcudescRel = heap_open(cudescOid, partitionLock); // 打开 CU 描述符关系cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OIDcudescIdxRel = 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); // 更新表空间 OIDrd_rel->relfilenode = newrelfilenode; // 更新文件节点 OIDsimple_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 分区表的处理呢?以下修改仅作为本人的一种想法,可供参考:

/** @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 描述符 OIDOid cudescIdxOid = InvalidOid; // CU 描述符索引 OIDOid 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 描述符 OIDcudescRel = table_open(cudescOid, partitionLock); // 打开 CU 描述符关系cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OIDcudescIdxRel = 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); // 更新表空间 OIDrd_rel->relfilenode = newrelfilenode; // 更新文件节点 OIDsimple_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 描述符 OIDcudescRel = table_open(cudescOid, partitionLock); // 打开 CU 描述符关系cudescIdxOid = cudescRel->rd_rel->relcudescidx; // 获取 CU 描述符索引 OIDcudescIdxRel = 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); // 更新表空间 OIDrd_rel->relfilenode = newrelfilenode; // 更新文件节点 OIDsimple_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); // 关闭父关系
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/34346.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

DeepSeek-Coder-v2击败GPT-4 Turbo,成为竞技场最强开源编码模型!

目录 01 编码与数学击败GPT-4 Turbo 02 深度求索&#xff1a;价格战的导火索 就在刚刚&#xff0c;竞技场排名再次刷新&#xff1a;深度求索的DeepSeek-Coder-v2成为竞技场最强开源编码模型&#xff01; 它在Coding Arena中已攀升至第4名&#xff0c;水平接近GPT-4 Turbo。 没…

mysql的information_schema浅析

information_schema 是 MySQL 中的一个虚拟数据库&#xff0c;它包含了关于 MySQL 服务器的所有元数据。 information_schema 作用 元数据管理&#xff1a;提供关于数据库、表、列、索引、权限等的信息。 性能优化&#xff1a;帮助了解数据库结构和索引使用情况&#xff0c;便…

Latex学习之“usefont”用法

Latex学习之“\usefont”用法 一、通俗的解释 \usefont 是 LaTeX 中的一个命令&#xff0c;用于在文档中临时改变字体&#xff0c;其基本语法如下&#xff1a; \usefont{字体编码}{字体族}{字体系列}{字体形状}这样看起来好像蛮抽象&#xff0c;你可能以及晕了&#xff0c;什…

QT中eventFilter的用途详解

QEventFilter 是 Qt 框架中的一个机制&#xff0c;用于拦截和处理事件。这在 Qt 的事件驱动模型中非常重要&#xff0c;因为它允许我们在事件到达目标对象之前捕获并处理它们。下面是对 QEventFilter 的详细解释&#xff0c;分为几个关键部分&#xff1a; 1. 事件处理机制 在…

Arduino称重传感器和 HX711 放大器(数字秤)

Arduino称重传感器和 HX711 放大器&#xff08;数字秤&#xff09; Arduino with Load Cell and HX711 Amplifier (Digital Scale) In this guide, you’ll learn how to create a digital scale with the Arduino using a load cell and the HX711 amplifier. First, you’l…

队列。。。

目的&#xff1a; 1&#xff0e;掌握队列存储结构的表示和实现方法。 2&#xff0e;掌握队列的入队和出队等基本操作的算法实现。 3&#xff0e;了解队列在解决实际问题中的简单应用。 要求&#xff1a; &#xff08;1&#xff09;根据输入的队列长度n和各元素值建立一个循环队…

Redis-使用 jedis 操作数据

文章目录 1、Jedis简介2、环境准备3、创建maven普通项目,导入如下依赖4、测试JAVA程序和Redis之间的通信 1、Jedis简介 "Jedis" 通常是作为 "Java Redis" 的缩写或简称来理解的。Java Embedded Data Structures Interface 表示 Java嵌入式数据结构接口 2、…

高德行政区查询-综合省市县三级选择跳转

一、需求&#xff1a; 需要使用高德地图进行省市县的一个选择&#xff0c;每选择一次就在地图上对选择的省市县进行定位并画出该区域的范围。 最终效果&#xff1a; 二、准备工作 高德的API的key&#xff1a;两种 三、完整页面代码 综合的是这两篇中的内容&#xff08;不…

19、删除链表的倒数第

1、题目描述 给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 …

Vue3从入门到精通

文章目录 前言为什么选择Vue框架Vue是什么?为什么要学习Vue? Vue简介Vue API风格选项式API(Options API)组合式API(Composition API) Vue开发前的准备创建Vue项目 Vue项目目录结构![在这里插入图片描述](https://img-blog.csdnimg.cn/direct/0400cdd482704d4d9ffa8a3a6687b12…

什么是档案业务建设评价

档案业务建设评价是对档案管理机构或部门在档案业务建设方面进行的评估和考核。评价主要根据一定的评价标准和指标&#xff0c;对档案业务的各个方面进行综合评估&#xff0c;包括档案收集、整理、保管、利用等环节&#xff0c;以及档案管理机构的组织管理、技术能力等方面。 评…

猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests

猫头虎 分享已解决Error || API Rate Limits: HTTP 429 Too Many Requests &#x1f42f; 摘要 &#x1f4c4; 大家好&#xff0c;我是猫头虎&#xff0c;一名专注于人工智能领域的博主。在AI开发中&#xff0c;我们经常会遇到各种各样的错误&#xff0c;其中API Rate Limits…

Redis-笔记(视频摘抄:哔哩哔哩博主(感谢!)-遇见狂神)

Redis&#xff08;缓存数据库&#xff0c;有效控制查询&#xff09;是非关系型数据库 缓存穿透、缓存击穿&#xff0c;缓存雪崩 Nosql概述 为什么使用NoSQL 大数据时代&#xff0c;那么什么是大数据&#xff0c;大数据就是一般的数据库没有办法进行分析处理&#xff0c;其中…

算法训练(leetcode)第十六天 | 530. 二叉搜索树的最小绝对差、501. 二叉搜索树中的众数、236. 二叉树的最近公共祖先

刷题记录 530. 二叉搜索树的最小绝对差递归非递归 501. 二叉搜索树中的众数*236. 二叉树的最近公共祖先 530. 二叉搜索树的最小绝对差 leetcode题目地址 如果是一颗普通树&#xff0c;则使用暴力求解法&#xff1a;遍历树并保存树种每个节点的值&#xff0c;排序后找差值最小…

【Unity】数据持久化 PlayerPrefs

1、PlayerPrefs是什么 是unity提供的可以用于存储读取玩家数据的公共类 2、存储相关 2.1 PlayerPrefs的数据存储类似于键值对存储一个键对应一个值 提供了存储3种数据的方法int float string 键: string类型 值: int float string对应3种API PlayerPrefs.SetInt("myAge…

Web 应用开源项目大全

Web 应用开源项目大全结合巴比达内网穿透实现WEB公开访问。 下面是一个Web应用的开源列表。没什么可说的&#xff0c;太疯狂了。尤其是Web 2.0那一堆。我不知道你怎么想&#xff0c;有些开源项目的源码写得挺不好的&#xff0c;尤其是性能方面。或许你会以为改一改他们就可以成…

java构造方法的重载

在java中&#xff0c;与普通方法一样&#xff0c;构造方法也可以重载&#xff0c;在一个类中可以定义多个构造方法&#xff0c;但是要求每个构造方法的参数类型或参数不同。在创建对象时&#xff0c;可以通过调用不同的构造方法为不同属性赋值。 示例代码如下 class Student5…

全球网络战市场规模未来十年将超过万亿元

报告称&#xff0c;网络战市场涉及组件、最终用户和地区&#xff0c;其中组件分为硬件、软件和服务&#xff0c;最终用户分为政府、企业和私人、航空航天和国防、BFSI&#xff08;银行、金融服务和保险&#xff09;、医疗保健等&#xff0c;地区涉及北美、欧洲、亚太地区和拉美…

python turtle 画帕恰狗

先上个图给大家看看 代码 ##作者V w1933423 import turtle turtle.bgcolor("#ece8dc") turtle.setup(600,900) p turtle.Pen() p.pensize(14) p.speed(5) p.color("black")p.penup() p.goto(-54,-44) p.pendown() p.goto(-37,-39) p.goto(-27,-24) p.go…

Unity如何保存玩家的数据(Unity的二进制序列化)

文章目录 什么是二进制序列化读写文件构造函数 自定义二进制序列化 什么是二进制序列化 Unity中的二进制序列化是一种将游戏对象或数据结构转换为二进制格式的过程&#xff0c;以便于存储或网络传输。这使数据能够以高效的方式保存&#xff0c;同时在需要时可以被正确地恢复&a…