合江网站建设建筑培训网官网证件查询
2026/4/17 1:52:52 网站建设 项目流程
合江网站建设,建筑培训网官网证件查询,如何搭建门户网站,电子商务基础网站建设与维护单项选择题文章目录本文内容一览#xff08;快速理解#xff09;一、什么是 TRUNCATE#xff08;Truncate Table#xff09;#xff1a;理解清空表数据的本质二、TRUNCATE 的执行流程#xff08;Execution Flow#xff09;#xff1a;从 SQL 到数据清空的 7 个阶段2.1 完整执行链…文章目录本文内容一览快速理解一、什么是 TRUNCATETruncate Table理解清空表数据的本质二、TRUNCATE 的执行流程Execution Flow从 SQL 到数据清空的 7 个阶段2.1 完整执行链路Complete Execution Chain7 个阶段的旅程2.2 阶段1-3SQL 解析到权限检查SQL Parsing to Authorization快速验证阶段2.4 阶段4执行入口Execution Entry路由到元数据操作2.3 阶段5元数据操作Metadata Operations核心执行阶段2.3.1 子阶段1读锁检查阶段Read Lock Check Phase快速验证2.3.2 子阶段2创建新分区阶段Create New Partitions Phase无锁操作2.3.3 子阶段3写锁替换阶段Write Lock Replace Phase关键阻塞点替换分区Replace PartitionsEditLog 写入EditLog Write关键阻塞点2.5 阶段6-7持久化和同步Persistence and Synchronization确保数据一致性三、锁机制深度解析Lock Mechanism为什么会被阻塞3.1 数据库锁类型Database Lock Types读锁和写锁的区别3.2 TRUNCATE 锁持有时间线Lock Holding Timeline理解阻塞过程3.3 锁竞争场景Lock Contention Scenarios实际影响分析四、性能瓶颈分析Performance Bottleneck Analysis找出问题根源4.1 各阶段耗时统计Stage Time Statistics找出慢的地方4.2 性能瓶颈分析Performance Bottleneck Analysis三大瓶颈五、优化建议Optimization Recommendations如何避免问题5.1 短期优化Short-term Optimization不修改核心逻辑5.2 长期优化Long-term Optimization需要代码修改 本章总结适合对象StarRocks 开发者、运维人员、对数据库内部机制感兴趣的初学者⏱️预计阅读时间40-50分钟学习目标理解 TRUNCATE 语句在 StarRocks 中的完整执行流程掌握锁机制和性能瓶颈第一步理解 TRUNCATE 是什么清空表数据第二步了解执行流程的7个阶段重点第三步理解锁机制为什么会被阻塞第四步认识性能瓶颈EditLog 写入第五步掌握优化方法如何避免问题本文内容一览快速理解TRUNCATE 的本质清空表数据通过创建新分区替换旧分区实现执行流程从 SQL 解析到数据清空经历 7 个关键阶段锁机制使用数据库级别的写锁在等待持久化时阻塞其他操作性能瓶颈EditLog 写入需要 1-5 秒期间一直持有写锁优化方向降低频率、使用分区表、异步写入等一、什么是 TRUNCATETruncate Table理解清空表数据的本质这一章要建立的基础理解 TRUNCATE 语句的作用和实现原理核心问题当我们执行TRUNCATE TABLE db.tbl时StarRocks 内部到底发生了什么[!NOTE] 关键点总结TRUNCATE 不是删除数据而是用新的空分区替换旧分区这样速度更快概念的本质TRUNCATE 是数据库提供的一种快速清空表数据的方法。与 DELETE 不同TRUNCATE 不是逐行删除数据而是通过替换分区的方式实现清空。图解说明旧分区包含数据创建新分区空分区替换操作用新分区替换旧分区结果表被清空但结构保留说明TRUNCATE 的优势是速度快因为它不需要逐行删除数据而是直接替换整个分区实际例子-- 清空整个表TRUNCATETABLEmy_db.user_table;-- 只清空指定分区TRUNCATETABLEmy_db.user_tablePARTITION(p20251210);二、TRUNCATE 的执行流程Execution Flow从 SQL 到数据清空的 7 个阶段核心问题一条 TRUNCATE SQL 语句是如何一步步执行完成的[!NOTE] 关键点总结TRUNCATE 执行分为 7 个阶段其中第 5 阶段的写锁替换是最关键的阻塞点2.1 完整执行链路Complete Execution Chain7 个阶段的旅程流程概览阶段1SQL 解析解析语法树阶段2语义分析验证表名和分区阶段3权限检查验证用户权限阶段4执行入口路由到元数据操作阶段5元数据操作核心阶段阶段6EditLog 持久化写入 BDBJE阶段7BE 节点同步同步到后端节点5.1 读锁检查检查表信息5.2 创建新分区无锁操作5.3 写锁替换阻塞点各阶段耗时统计阶段操作锁类型耗时是否阻塞1. SQL 解析语法解析无 1ms否2. 语义分析表名规范化无 1ms否3. 权限检查权限验证无 1ms否4. 读锁检查表信息检查读锁1-10ms否5. 创建分区创建新分区无锁100-500ms否6. 写锁替换替换分区写锁1-5秒是7. EditLog 写入BDBJE 持久化写锁持有1-5秒是说明阶段 6 和 7 是性能瓶颈因为需要等待 BDBJE 写入完成期间一直持有写锁实际例子假设执行TRUNCATE TABLE my_db.orders PARTITION(p20251210)时间轴 T0 (0ms): 开始执行 TRUNCATE T1 (1ms): SQL 解析完成 T2 (2ms): 语义分析完成 T3 (3ms): 权限检查通过 T4 (10ms): 读锁检查完成确认分区存在 T5 (300ms): 创建新分区完成无锁不阻塞 T6 (310ms): 获取写锁开始替换分区 T7 (350ms): 分区替换完成 T8 (350ms): 开始写入 EditLog T9 (3350ms):EditLog 写入完成等待了3秒 T10 (3400ms):释放写锁 T11 (3400ms):完成可以看到在 T8 到 T9 这 3 秒期间写锁一直被持有其他操作都被阻塞。2.2 阶段1-3SQL 解析到权限检查SQL Parsing to Authorization快速验证阶段阶段1SQL 解析SQL Parsing文件位置fe/fe-core/src/main/java/com/starrocks/sql/parser/AstBuilder.java关键源码OverridepublicParseNodevisitTruncateTableStatement(StarRocksParser.TruncateTableStatementContextcontext){QualifiedNamequalifiedNamegetQualifiedName(context.qualifiedName());TableNametargetTableNamequalifiedNameToTableName(qualifiedName);Tokenstartcontext.start;Tokenstopcontext.stop;PartitionNamespartitionNamesnull;if(context.partitionNames()!null){stopcontext.partitionNames().stop;partitionNames(PartitionNames)visit(context.partitionNames());}NodePositionposcreatePos(start,stop);returnnewTruncateTableStmt(newTableRef(targetTableName,null,partitionNames,pos));}AST 节点结构// 文件位置fe/fe-core/src/main/java/com/starrocks/sql/ast/TruncateTableStmt.javapublicclassTruncateTableStmtextendsDdlStmt{privatefinalTableReftblRef;// 包含表名和分区信息publicTableRefgetTblRef(){returntblRef;}publicStringgetDbName(){returntblRef.getName().getDb();}publicStringgetTblName(){returntblRef.getName().getTbl();}}功能说明解析 SQL 语法树提取表名和分区信息创建TruncateTableStmtAST 节点支持两种格式TRUNCATE TABLE db.tbl清空整个表TRUNCATE TABLE db.tbl PARTITION(p1, p2)清空指定分区阶段2语义分析Semantic Analysis文件位置fe/fe-core/src/main/java/com/starrocks/sql/analyzer/TruncateTableAnalyzer.java关键源码publicstaticvoidanalyze(TruncateTableStmtstatement,ConnectContextcontext){// 1. 规范化表名处理大小写、默认数据库等MetaUtils.normalizationTableName(context,statement.getTblRef().getName());// 2. 检查是否使用别名不支持if(statement.getTblRef().hasExplicitAlias()){thrownewSemanticException(Not support truncate table with alias);}// 3. 检查分区信息PartitionNamespartitionNamesstatement.getTblRef().getPartitionNames();if(partitionNames!null){// 不支持清空临时分区if(partitionNames.isTemp()){thrownewSemanticException(Not support truncate temp partitions);}// 检查分区名是否为空if(partitionNames.getPartitionNames().stream().anyMatch(entity-Strings.isNullOrEmpty(entity))){thrownewSemanticException(there are empty partition name);}}}调用路径// 文件位置fe/fe-core/src/main/java/com/starrocks/sql/analyzer/AnalyzerVisitor.javaOverridepublicVoidvisitTruncateTableStatement(TruncateTableStmtstatement,ConnectContextcontext){TruncateTableAnalyzer.analyze(statement,context);returnnull;}功能说明规范化表名处理大小写、默认数据库验证语法约束不支持别名、不支持临时分区验证分区名有效性阶段3权限检查Authorization文件位置fe/fe-core/src/main/java/com/starrocks/sql/analyzer/AuthorizerStmtVisitor.java关键源码OverridepublicVoidvisitTruncateTableStatement(TruncateTableStmtstatement,ConnectContextcontext){// 检查用户是否有 TRUNCATE 权限Authorizer.checkTableAction(context.getCurrentUserIdentity(),context.getCurrentRoleIds(),statement.getDbName(),statement.getTblName(),PrivilegeType.DELETE);returnnull;}功能说明验证用户是否有表的 DELETE 权限TRUNCATE 使用 DELETE 权限如果权限不足抛出AccessDeniedException2.4 阶段4执行入口Execution Entry路由到元数据操作文件位置fe/fe-core/src/main/java/com/starrocks/qe/DDLStmtExecutor.java关键源码OverridepublicShowResultSetvisitTruncateTableStatement(TruncateTableStmtstmt,ConnectContextcontext){ErrorReport.wrapWithRuntimeException(()-{context.getGlobalStateMgr().truncateTable(stmt);});returnnull;}调用链// 文件位置fe/fe-core/src/main/java/com/starrocks/server/GlobalStateMgr.javapublicvoidtruncateTable(TruncateTableStmttruncateTableStmt)throwsDdlException{localMetastore.truncateTable(truncateTableStmt);}功能说明将执行委托给GlobalStateMgr再转发到LocalMetastore使用ErrorReport.wrapWithRuntimeException包装异常2.3 阶段5元数据操作Metadata Operations核心执行阶段核心问题如何在不影响数据一致性的前提下快速清空表数据2.3.1 子阶段1读锁检查阶段Read Lock Check Phase快速验证操作流程获取读锁db.readLock()检查表是否存在检查表类型只支持 OLAP/LAKE 表检查表状态必须是 NORMAL收集分区信息创建表的影子副本释放读锁db.readUnlock()关键操作获取读锁db.readLock()- 数据库级别的读锁共享锁验证表状态检查表是否存在、类型是否支持、状态是否正常收集分区信息根据是否指定分区收集需要清空的分区列表创建影子副本创建表的副本用于后续创建新分区释放读锁db.readUnlock()锁持有时间通常 1-10ms不会阻塞其他读操作关键源码文件位置fe/fe-core/src/main/java/com/starrocks/server/LocalMetastore.java代码位置LocalMetastore.java:4495-4531// 1. 获取数据库读锁检查阶段db.readLock();try{Tabletabledb.getTable(dbTbl.getTbl());if(tablenull){ErrorReport.reportDdlException(ErrorCode.ERR_BAD_TABLE_ERROR,dbTbl.getTbl());}// 只支持 OLAP 表或 LAKE 表if(!table.isOlapOrCloudNativeTable()){thrownewDdlException(Only support truncate OLAP table or LAKE table);}OlapTableolapTable(OlapTable)table;if(olapTable.getState()!OlapTable.OlapTableState.NORMAL){throwInvalidOlapTableStateException.of(olapTable.getState(),olapTable.getName());}// 收集需要清空的分区信息if(!truncateEntireTable){// 清空指定分区for(StringpartName:tblRef.getPartitionNames().getPartitionNames()){PartitionpartitionolapTable.getPartition(partName);if(partitionnull){thrownewDdlException(Partition partName does not exist);}origPartitions.put(partName,partition);GlobalStateMgr.getCurrentState().getAnalyzeMgr().recordDropPartition(partition.getId());}}else{// 清空整个表的所有分区for(Partitionpartition:olapTable.getPartitions()){origPartitions.put(partition.getName(),partition);GlobalStateMgr.getCurrentState().getAnalyzeMgr().recordDropPartition(partition.getId());}}// 创建表的影子副本用于后续创建新分区copiedTblgetShadowCopyTable(olapTable);}finally{db.readUnlock();// 释放读锁}实际例子这段代码展示了读锁检查阶段的完整流程包括表存在性检查、类型验证、状态检查、分区信息收集和影子副本创建。2.3.2 子阶段2创建新分区阶段Create New Partitions Phase无锁操作操作流程遍历旧分区生成新分区ID复制分区属性存储介质、副本数等创建新分区构建分区结构创建 Tablet、索引完成关键操作生成新分区ID为每个要清空的分区生成新的分区ID复制分区属性从旧分区复制存储介质、副本数、数据属性等配置创建新分区调用createPartition()创建新分区构建分区结构调用buildPartitions()创建 Tablet 和索引结构错误处理如果创建失败清理已创建的 Tablet特点无锁操作此阶段不持有任何锁不会阻塞其他操作耗时较长创建分区和 Tablet 需要 100-500ms可回滚如果失败会清理已创建的资源实际例子假设要清空 3 个分区时间轴 T0: 开始创建新分区无锁 T1 (50ms): 创建分区1完成 T2 (150ms): 创建分区2完成 T3 (300ms): 创建分区3完成所有新分区创建完成在这 300ms 期间其他操作可以正常进行不会被阻塞。关键源码文件位置fe/fe-core/src/main/java/com/starrocks/server/LocalMetastore.java代码位置LocalMetastore.java:4533-4566// 2. 使用影子副本创建新分区无锁操作ListPartitionnewPartitionsLists.newArrayListWithCapacity(origPartitions.size());SetLongtabletIdSetSets.newHashSet();try{for(Map.EntryString,Partitionentry:origPartitions.entrySet()){longoldPartitionIdentry.getValue().getId();longnewPartitionIdgetNextId();// 生成新的分区IDStringnewPartitionNameentry.getKey();// 复制分区属性存储介质、副本数、数据属性等PartitionInfopartitionInfocopiedTbl.getPartitionInfo();partitionInfo.setTabletType(newPartitionId,partitionInfo.getTabletType(oldPartitionId));partitionInfo.setIsInMemory(newPartitionId,partitionInfo.getIsInMemory(oldPartitionId));partitionInfo.setReplicationNum(newPartitionId,partitionInfo.getReplicationNum(oldPartitionId));partitionInfo.setDataProperty(newPartitionId,partitionInfo.getDataProperty(oldPartitionId));if(copiedTbl.isCloudNativeTable()){partitionInfo.setDataCacheInfo(newPartitionId,partitionInfo.getDataCacheInfo(oldPartitionId));}copiedTbl.setDefaultDistributionInfo(entry.getValue().getDistributionInfo());// 创建新分区PartitionnewPartitioncreatePartition(db,copiedTbl,newPartitionId,newPartitionName,null,tabletIdSet);newPartitions.add(newPartition);}// 构建分区创建 Tablet、索引等buildPartitions(db,copiedTbl,newPartitions.stream().map(Partition::getSubPartitions).flatMap(p-p.stream()).collect(Collectors.toList()));}catch(DdlExceptione){// 如果创建失败清理已创建的 TabletdeleteUselessTablets(tabletIdSet);throwe;}这段代码展示了如何创建新分区生成新分区ID、复制分区属性、创建分区结构以及错误处理机制。2.3.3 子阶段3写锁替换阶段Write Lock Replace Phase关键阻塞点操作流程获取写锁db.writeLock()再次检查表状态防止表被删除检查分区是否变化检查元数据是否变化替换分区核心操作更新 Colocation 信息写入 EditLog阻塞点刷新物化视图释放写锁db.writeUnlock()关键操作详解替换分区Replace Partitions文件位置fe/fe-core/src/main/java/com/starrocks/server/LocalMetastore.java代码位置LocalMetastore.java:4670-4702关键源码privatevoidtruncateTableInternal(OlapTableolapTable,ListPartitionnewPartitions,booleanisEntireTable,booleanisReplay){// 使用新分区替换旧分区SetTabletoldTabletsSets.newHashSet();for(PartitionnewPartition:newPartitions){PartitionoldPartitionolapTable.replacePartition(newPartition);// ← 替换操作for(PhysicalPartitionphysicalPartition:oldPartition.getSubPartitions()){// 收集旧 Tablet 用于后续删除for(MaterializedIndexindex:physicalPartition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)){// let HashSet do the deduplicate workoldTablets.addAll(index.getTablets());}}}if(isEntireTable){// 如果是清空整个表删除所有临时分区olapTable.dropAllTempPartitions();}// 从 InvertedIndex 中删除旧 Tabletfor(Tablettablet:oldTablets){TabletInvertedIndexindexGlobalStateMgr.getCurrentInvertedIndex();index.deleteTablet(tablet.getId());// 确保只有 Leader FE 记录 truncate 信息if(!isReplay){index.markTabletForceDelete(tablet);}}}功能说明使用新创建的空分区替换旧分区收集旧 Tablet 并标记删除如果是清空整个表删除所有临时分区EditLog 写入EditLog Write关键阻塞点文件位置fe/fe-core/src/main/java/com/starrocks/server/LocalMetastore.java代码位置LocalMetastore.java:4568-4656写锁替换阶段完整源码// 3. 获取数据库写锁关键操作阶段db.writeLock();// ← 关键数据库级别的写锁try{// 3.1 再次检查表状态防止在创建分区期间表被删除或修改OlapTableolapTable(OlapTable)db.getTable(copiedTbl.getId());if(olapTablenull){thrownewDdlException(Table[copiedTbl.getName()] is dropped);}if(olapTable.getState()!OlapTable.OlapTableState.NORMAL){throwInvalidOlapTableStateException.of(olapTable.getState(),olapTable.getName());}// 3.2 检查分区是否发生变化for(Map.EntryString,Partitionentry:origPartitions.entrySet()){PartitionpartitionolapTable.getPartition(entry.getValue().getId());if(partitionnull||!partition.getName().equalsIgnoreCase(entry.getKey())){thrownewDdlException(Partition [entry.getKey()] is changed during truncating table, please retry);}}// 3.3 检查元数据是否发生变化Schema、索引等booleanmetaChangedfalse;if(olapTable.getIndexNameToId().size()!copiedTbl.getIndexNameToId().size()){metaChangedtrue;}else{// 比较 SchemaHashMapLong,IntegercopiedIndexIdToSchemaHashcopiedTbl.getIndexIdToSchemaHash();for(Map.EntryLong,Integerentry:olapTable.getIndexIdToSchemaHash().entrySet()){longindexIdentry.getKey();if(!copiedIndexIdToSchemaHash.containsKey(indexId)){metaChangedtrue;break;}if(!copiedIndexIdToSchemaHash.get(indexId).equals(entry.getValue())){metaChangedtrue;break;}}}if(olapTable.getDefaultDistributionInfo().getType()!copiedTbl.getDefaultDistributionInfo().getType()){metaChangedtrue;}if(metaChanged){thrownewDdlException(Table[copiedTbl.getName()]s meta has been changed. try again.);}// 3.4 替换分区核心操作truncateTableInternal(olapTable,newPartitions,truncateEntireTable,false);// 3.5 更新 Colocation 信息try{colocateTableIndex.updateLakeTableColocationInfo(olapTable,true/* isJoin */,null/* expectGroupId */);}catch(DdlExceptione){LOG.info(table {} update colocation info failed when truncate table, {},olapTable.getId(),e.getMessage());}// 3.6 写入 EditLog阻塞点TruncateTableInfoinfonewTruncateTableInfo(db.getId(),olapTable.getId(),newPartitions,truncateEntireTable);GlobalStateMgr.getCurrentState().getEditLog().logTruncateTable(info);// ← 阻塞等待 BDBJE 写入// 3.7 刷新物化视图SetMvIdrelatedMvsolapTable.getRelatedMaterializedViews();for(MvIdmvId:relatedMvs){MaterializedViewmaterializedView(MaterializedView)getTable(mvId.getDbId(),mvId.getId());if(materializedViewnull){LOG.warn(Table related materialized view {}.{} can not be found,mvId.getDbId(),mvId.getId());continue;}if(materializedView.isLoadTriggeredRefresh()){DatabasemvDbgetDb(mvId.getDbId());refreshMaterializedView(mvDb.getFullName(),getTable(mvDb.getId(),mvId.getId()).getName(),false,null,Constants.TaskRunPriority.NORMAL.value(),true,false);}}}catch(DdlExceptione){deleteUselessTablets(tabletIdSet);throwe;}catch(MetaNotFoundExceptione){LOG.warn(Table related materialized view can not be found,e);}finally{db.writeUnlock();// 释放写锁}EditLog 写入源码文件位置fe/fe-core/src/main/java/com/starrocks/persist/EditLog.javalogTruncateTable 方法EditLog.java:1789-1791publicvoidlogTruncateTable(TruncateTableInfoinfo){logEdit(OperationType.OP_TRUNCATE_TABLE,info);}logEdit 方法EditLog.java:1243-1246protectedvoidlogEdit(shortop,Writablewritable){JournalTasktasksubmitLog(op,writable,-1);waitInfinity(task);// ← 阻塞等待 BDBJE 写入完成}waitInfinity 方法EditLog.java:1299-1324关键阻塞点publicstaticvoidwaitInfinity(JournalTasktask){longstartTimeNanotask.getStartTimeNano();booleanresult;intcnt0;while(true){try{if(cnt!0){Thread.sleep(1000);// 失败后等待1秒重试}// 等待 JournalWriter 写入完成resulttask.get();// ← 阻塞等待break;}catch(InterruptedException|ExecutionExceptione){LOG.warn(failed to wait, wait and retry {} times..: {},cnt,e);cnt;}}assert(result);if(MetricRepo.hasInit){MetricRepo.HISTO_EDIT_LOG_WRITE_LATENCY.update((System.nanoTime()-startTimeNano)/1000000);}}阻塞机制分析TRUNCATE线程EditLog任务队列JournalWriter线程BDBJE存储logTruncateTable(info)提交日志任务waitInfinity() 阻塞等待取出任务写入 BDBJE写入完成通知完成继续执行TRUNCATE线程EditLog任务队列JournalWriter线程BDBJE存储关键问题写锁持有时间长在等待 BDBJE 写入期间一直持有数据库写锁阻塞所有读操作写锁持有期间所有需要读锁的操作如 ReportHandler都被阻塞BDBJE 写入耗时正常情况下 1-5 秒高负载时可能更长实际例子时间轴 T0: TRUNCATE 获取写锁 T1: 替换分区完成50ms T2: 开始写入 EditLog T3: 等待 BDBJE 写入...3秒 T4: BDBJE 写入完成 T5: 释放写锁 在这 3 秒期间T2-T4写锁一直被持有2.5 阶段6-7持久化和同步Persistence and Synchronization确保数据一致性阶段6EditLog 持久化EditLog Persistence文件位置fe/fe-core/src/main/java/com/starrocks/journal/bdbje/TruncateTableInfo 数据结构文件位置fe/fe-core/src/main/java/com/starrocks/persist/TruncateTableInfo.javapublicclassTruncateTableInfoimplementsWritable{SerializedName(valuedbId)privatelongdbId;// 数据库IDSerializedName(valuetblId)privatelongtblId;// 表IDSerializedName(valuepartitions)privateListPartitionpartitions;// 新分区列表SerializedName(valueisEntireTable)privatebooleanisEntireTable;// 是否清空整个表publicTruncateTableInfo(longdbId,longtblId,ListPartitionpartitions,booleanisEntireTable){this.dbIddbId;this.tblIdtblId;this.partitionspartitions;this.isEntireTableisEntireTable;}Overridepublicvoidwrite(DataOutputout)throwsIOException{StringjsonGsonUtils.GSON.toJson(this);// 序列化为 JSONText.writeString(out,json);}}流程说明JournalWriter 线程从队列中取出日志任务序列化将TruncateTableInfo序列化为 JSONBDBJE 写入写入 Berkeley DB Java Edition持久化存储同步等待等待写入完成同步写入回调通知通知等待的线程阶段7BE 节点同步Backend Node Synchronization回放方法源码文件位置fe/fe-core/src/main/java/com/starrocks/server/LocalMetastore.java代码位置LocalMetastore.java:4704-4730publicvoidreplayTruncateTable(TruncateTableInfoinfo){DatabasedbgetDb(info.getDbId());db.writeLock();try{OlapTableolapTable(OlapTable)db.getTable(info.getTblId());truncateTableInternal(olapTable,info.getPartitions(),info.isEntireTable(),true);if(!GlobalStateMgr.isCheckpointThread()){// 将新 Tablet 添加到 InvertedIndexTabletInvertedIndexinvertedIndexGlobalStateMgr.getCurrentInvertedIndex();for(Partitionpartition:info.getPartitions()){longpartitionIdpartition.getId();TStorageMediummediumolapTable.getPartitionInfo().getDataProperty(partitionId).getStorageMedium();for(PhysicalPartitionphysicalPartition:partition.getSubPartitions()){for(MaterializedIndexmIndex:physicalPartition.getMaterializedIndices(MaterializedIndex.IndexExtState.ALL)){// 添加 Tablet 到索引// ...}}}}}finally{db.writeUnlock();}}流程说明EditLog 回放Follower FE 节点回放 EditLog元数据同步BE 节点通过心跳获取元数据变更Tablet 清理BE 节点删除旧 Tablet 的数据文件三、锁机制深度解析Lock Mechanism为什么会被阻塞这一章要建立的基础理解 StarRocks 的锁机制明白为什么 TRUNCATE 会阻塞其他操作核心问题为什么 TRUNCATE 执行时其他操作会被阻塞[!NOTE] 关键点总结TRUNCATE 使用数据库级别的写锁在等待持久化时一直持有锁导致其他操作被阻塞3.1 数据库锁类型Database Lock Types读锁和写锁的区别锁类型StarRocks 使用两种类型的锁读锁ReadLockdb.readLock()- 共享锁多个读操作可以并发写锁WriteLockdb.writeLock()- 排他锁独占访问锁实现源码文件位置fe/fe-core/src/main/java/com/starrocks/catalog/Database.javapublicclassDatabaseextendsMetaObject{privatefinalReentrantReadWriteLockrwLocknewReentrantReadWriteLock(true);publicvoidreadLock(){longstartMsTimeUnit.MILLISECONDS.convert(System.nanoTime(),TimeUnit.NANOSECONDS);StringthreadDumpgetOwnerInfo(rwLock.getOwner());this.rwLock.sharedLock();// 获取共享锁读锁logSlowLockEventIfNeeded(startMs,readLock,threadDump);}publicvoidwriteLock(){longstartMsTimeUnit.MILLISECONDS.convert(System.nanoTime(),TimeUnit.NANOSECONDS);StringthreadDumpgetOwnerInfo(rwLock.getOwner());this.rwLock.exclusiveLock();// 获取排他锁写锁logSlowLockEventIfNeeded(startMs,writeLock,threadDump);}publicvoidreadUnlock(){this.rwLock.sharedUnlock();}publicvoidwriteUnlock(){this.rwLock.exclusiveUnlock();}}图解说明读锁 ReadLock共享锁多个读操作可以同时进行写锁 WriteLock排他锁独占访问阻塞所有其他操作实际例子// 读锁多个操作可以同时获取线程1:db.readLock()// 获取读锁线程2:db.readLock()// 也可以获取读锁共享线程3:db.readLock()// 也可以获取读锁共享// 三个线程可以同时读取// 写锁独占访问线程1:db.writeLock()// 获取写锁线程2:db.readLock()// 被阻塞必须等待线程1释放写锁线程3:db.writeLock()// 被阻塞必须等待线程1释放写锁3.2 TRUNCATE 锁持有时间线Lock Holding Timeline理解阻塞过程时间线分析000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms读锁检查创建新分区获取写锁替换分区写入EditLog刷新物化视图释放写锁读锁阶段无锁阶段写锁阶段阻塞TRUNCATE 锁持有时间线关键发现写锁持有时间从获取写锁到释放约 1-5 秒阻塞时间EditLog 写入期间1-5秒一直持有写锁阻塞影响写锁持有期间所有读锁操作被阻塞实际例子时间轴 T0: 开始执行 TRUNCATE T1: 获取读锁 (db.readLock) T2: 检查表信息 (1-10ms) T3: 释放读锁 (db.readUnlock) T4: 创建新分区 (无锁100-500ms) T5: 获取写锁 (db.writeLock) ← 关键点 T6: 替换分区 (10-50ms) T7: 写入 EditLog (logTruncateTable) T8: 等待 BDBJE 写入 (1-5秒) ← 阻塞点 T9: BDBJE 写入完成 T10: 刷新物化视图 (可选100-500ms) T11: 释放写锁 (db.writeUnlock) T12: 完成3.3 锁竞争场景Lock Contention Scenarios实际影响分析场景1TRUNCATE ReportHandler时间线TRUNCATE线程ReportHandler线程数据库锁获取写锁尝试获取读锁被阻塞等待 BDBJE 写入1-5秒释放写锁获取读锁成功TRUNCATE线程ReportHandler线程数据库锁结果ReportHandler 被阻塞 1-5 秒可能导致 BE 心跳超时场景2多个 TRUNCATE 并发时间线000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms000 ms执行等待执行TRUNCATE1TRUNCATE2多个 TRUNCATE 串行执行结果多个 TRUNCATE 串行执行总耗时 N × (1-5秒)实际例子假设有 3 个 TRUNCATE 操作TRUNCATE1: 0-3秒持有写锁 TRUNCATE2: 3-6秒等待 TRUNCATE1然后执行 TRUNCATE3: 6-9秒等待 TRUNCATE2然后执行 总耗时9秒串行执行四、性能瓶颈分析Performance Bottleneck Analysis找出问题根源这一章要建立的基础理解 TRUNCATE 的性能瓶颈知道哪些地方可以优化核心问题为什么 TRUNCATE 会阻塞其他操作主要瓶颈在哪里[!NOTE] 关键点总结EditLog 写入是主要瓶颈在持有写锁期间等待 BDBJE 写入完成阻塞所有读操作4.1 各阶段耗时统计Stage Time Statistics找出慢的地方耗时对比表阶段操作平均耗时最大耗时是否可优化SQL 解析语法解析 1ms 5ms否语义分析表名规范化 1ms 5ms否读锁检查表信息检查1-10ms50ms否创建分区创建新分区100-500ms2秒是异步写锁替换替换分区10-50ms200ms否EditLog 写入BDBJE 持久化1-5秒10秒是异步刷新物化视图MV 刷新100-500ms2秒是异步可视化分析80%15%5%各阶段耗时占比典型情况EditLog 写入创建分区其他阶段说明EditLog 写入占总耗时的 80%是主要瓶颈4.2 性能瓶颈分析Performance Bottleneck Analysis三大瓶颈瓶颈1EditLog 写入阻塞EditLog Write Blocking问题在持有写锁期间等待 BDBJE 写入完成阻塞所有读操作 1-5 秒优化方向方案1异步写入 EditLog需要处理一致性方案2优化 BDBJE 写入性能硬件、配置方案3减少 EditLog 写入频率批量写入瓶颈2创建分区耗时Partition Creation Time问题创建分区和 Tablet 需要 100-500ms虽然无锁但增加总耗时优化方向方案1预创建分区池方案2优化 Tablet 创建逻辑瓶颈3锁粒度Lock Granularity问题使用数据库级别的写锁不是表级别同一数据库下的所有操作竞争同一把锁优化方向方案1改为表级别锁需要大量重构方案2使用更细粒度的锁分区级别五、优化建议Optimization Recommendations如何避免问题这一章要建立的基础掌握优化 TRUNCATE 性能的方法避免阻塞问题核心问题如何优化 TRUNCATE 操作减少对系统的影响[!NOTE] 关键点总结优化方向包括降低频率、使用分区表、增加超时配置、异步写入等5.1 短期优化Short-term Optimization不修改核心逻辑方案1降低 TRUNCATE 频率方法错开执行时间使用队列控制并发实际例子# 将 200 个任务分散到不同时间点# 例如每 30 秒执行一个任务# 200 个任务 × 30 秒 6000 秒 100 分钟方案2增加超时配置配置项catalog_try_lock_timeout_ms 30000数据库锁超时时间thrift_rpc_timeout_ms 30000Thrift RPC 超时时间方案3使用分区表优势只清空需要的分区减少锁持有时间实际例子-- 推荐只清空需要的分区TRUNCATETABLEordersPARTITION(p20251210);-- 不推荐清空整个表TRUNCATETABLEorders;5.2 长期优化Long-term Optimization需要代码修改方案1异步 EditLog 写入思路在替换分区后立即释放写锁异步写入 EditLog需要处理一致性问题方案2表级别锁思路将数据库级别锁改为表级别锁需要大量重构方案3批量 EditLog 写入思路将多个操作合并为一个 EditLog减少 BDBJE 写入次数 本章总结核心要点回顾TRUNCATE 的本质通过创建新分区替换旧分区实现快速清空执行流程7 个阶段其中写锁替换阶段是关键阻塞点锁机制使用数据库级别的写锁在等待持久化时阻塞其他操作性能瓶颈EditLog 写入耗时 1-5 秒占总耗时的 80%优化方向降低频率、使用分区表、异步写入等知识地图TRUNCATE 语句SQL 解析语义分析权限检查元数据操作读锁检查创建新分区写锁替换EditLog 写入瓶颈BE 节点同步关键决策点是否使用 TRUNCATE如果需要快速清空表TRUNCATE 比 DELETE 快频率控制单个数据库建议 ≤ 1-2 次/分钟分区策略使用分区表只清空需要的分区超时配置根据实际情况调整超时时间

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询