TiDB REFRESH STATS 命令设计解析:跨节点统计信息热刷新机制
发布时间:2026/9/10 3:07:43
分类:文化教育
浏览:1234

TiDB REFRESH STATS 命令设计解析跨节点统计信息热刷新机制【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidbTiDB 的优化器依赖统计信息Statistics选择执行计划但统计信息通常只在节点启动或执行ANALYZE时加载进内存。本设计文档 docs/design/2025-05-26-refresh-stats-command.md 提出了一条全新的 SQL 命令REFRESH STATS用于在不重启集群、不重新 ANALYZE 的前提下热刷新指定表的统计信息并借助 coprocessor 广播框架实现单节点下发、全集群生效。阅读本文后你将掌握该命令的完整语法、Lite/FULL 刷新语义、基于 Broadcast Query Executor 的跨节点广播原理以及并发控制、超时与权限设计等关键实现细节。一、设计动机为什么需要热刷新统计信息统计信息是优化器估算行数、选择索引和执行计划的核心依据。在 TiDB 中统计信息以表为单位加载进各 TiDB 节点的内存缓存中供查询优化使用。该设计文档指出两个核心动机加速统计信息备份恢复场景为满足关键客户对统计信息备份/恢复速度的要求TiDB 计划对统计信息相关表做物理备份与恢复避免逐行读写 JSON 文件带来的开销。但物理恢复发生在集群启动之后此时统计信息已完成初始化恢复出的最新数据不会自动进入内存。此前的临时方案是让用户全量重启所有 TiDB 节点以触发启动期统计信息初始化体验很差。新命令让 BR 工具在恢复完成后主动触发统计信息刷新。调试内存中的统计信息问题当怀疑某节点内存中的统计信息异常时可以强制刷新内存统计信息无需重启集群或重新执行 ANALYZE。从源码注释看这一命令在 TiDB 中已经落地实现见 pkg/executor/simple.go 中的executeRefreshStats、broadcast等函数本文结合设计文档与当前仓库源码进行展开。二、命令语法与语义2.1 语法总览设计文档给出的命令形态如下REFRESH STATS TARGETS [FULL | LITE] [CLUSTER]; REFRESH STATS db1.tbl1, tbl2, db2.*, *.* FULL CLUSTER;各组成部分语义如下TARGETS以逗号分隔的目标列表支持四种粒度tab1当前 schema数据库下的表db.tab1指定数据库下的指定表db.*指定数据库下的所有表*.*整个集群中的所有表。FULL | LITE指定刷新类型。省略时取值来自配置项lite-init-stats该配置在 TiDB v7.1.0 引入显式FULL等价于lite-init-statsfalse即初始化完整统计信息显式LITE等价于lite-init-statstrue即只加载轻量统计信息如表行数、粗略的元数据适合大规模场景下加速加载。CLUSTER设置后对所有 TiDB 实例刷新统计信息省略时只刷新当前所连接的那一个 TiDB 实例。权限要求目标表具有SELECT权限的用户以及具有ADMIN_RESTORE权限的 BR 工具可以使用该命令刷新指定表的统计信息。2.2 目标对象的解析在语法层面REFRESH STATS的目标列表会被解析为ast.RefreshStatsStmt其中每个目标是一个ast.RefreshObject通过StatsObjectScope区分作用域Global / Database / Table。设计文档用 yacc 文法给出了解析规则Identifier表示当前库下的表Identifier . Identifier表示db.tableIdentifier . *表示库下全部表* . *表示全集群表。从当前仓库源码看实际执行时RefreshStatsStmt会在构建阶段做去重s.Dedup()且要求目标对象在广播前必须是数据库限定名形式——见 pkg/executor/simple.go 中executeRefreshStats的断言逻辑REFRESH STATS tbl这种省略库名的写法在执行前会被restoreRefreshStatsSQL还原为完整限定名如REFRESH STATS db.tbl后再广播否则远端节点没有当前库上下文会跳过该表。三、核心设计利用 coprocessor 框架实现跨节点广播3.1 总体思路统计信息存储在每个 TiDB 节点的内存中用户包括 BR通常只连接单个 TiDB 节点。如何让一条命令在一台节点上执行后其余节点的统计信息也同步刷新是整个设计的最大挑战。设计文档参考了 TiDB 已有的KILL语句实现KILL能终止集群内任意 TiDB 实例上的连接正是通过 coprocessor 框架把KILL命令分发到不同 TiDB 节点。REFRESH STATS采用同样的思路——实现一个新的 coprocessor 算子operator在不同 TiDB 节点间发送并处理 coprocessor 请求。同时plan cache 模块也有类似需求跨节点刷新表的 plan cache因此该基础设施被设计为对统计信息刷新与plan cache 清理两类操作均可复用。3.2 Broadcast Query Executor首先在 protobuf 文件中定义新的执行器类型用于在 TiDB 节点之间传递 SQL 语句enum ExecType { ... TypeBroadcastQuery 17; } message Executor { ... optional BroadcastQuery broadcast_query 21; } message BroadcastQuery { optional string query 1; }在 SQL 层REFRESH STATS被解析为ast.RefreshStatsStmt后由 SimpleExec 执行器处理见 pkg/executor/simple.go。核心执行逻辑分为三层func (e *SimpleExec) executeRefreshStats(ctx context.Context, s *ast.RefreshStatsStmt) error { // 1. 还原为限定名 SQL如 db.tbl便于跨节点广播 sql, err : restoreRefreshStatsSQL(s) ... if e.IsFromRemote { // 2a. 远端节点真正执行本地刷新 return e.executeRefreshStatsOnCurrentInstance(ctx, s) } if s.IsClusterWide { // 2b. 协调节点 CLUSTER广播到所有 TiDB 节点 return broadcast(ctx, e.Ctx(), sql) } // 2c. 仅当前实例刷新 return e.executeRefreshStatsOnCurrentInstance(ctx, s) }本地执行路径executeRefreshStatsOnCurrentInstance根据目标作用域通过事务内信息 schemasessiontxn.GetTxnManager(...).GetTxnInfoSchema()解析出数据库与表 ID不存在的库/表会追加 warning如ErrDatabaseNotExists、ErrTableNotExists并跳过而不是直接报错终止。广播执行路径broadcast构造一个TypeBroadcastQuery类型的tipb.Executor装入 DAG 请求后下发给各节点func broadcast(ctx context.Context, sctx sessionctx.Context, sql string) error { broadcastExec : tipb.Executor{ Tp: tipb.ExecType_TypeBroadcastQuery, BroadcastQuery: tipb.BroadcastQuery{ Query: sql, }, } dagReq : tipb.DAGRequest{} ... dagReq.Executors []*tipb.Executor{broadcastExec} var builder distsql.RequestBuilder kvReq, err : builder. SetDAGRequest(dagReq). SetFromSessionVars(sctx.GetDistSQLCtx()). SetFromInfoSchema(sctx.GetInfoSchema()). SetStoreType(kv.TiDB). // 只发给 TiDB 节点 SetTiDBServerID(0). // 每个 TiDB 节点各生成一个任务 SetStartTS(math.MaxUint64). // 保证可见性检查通过 Build() ... resp : sctx.GetClient().Send(ctx, kvReq, sctx.GetSessionVars().KVVars, kv.ClientSendOption{}) ... for { subset, err : resp.Next(ctx) ... if subset nil { break // 所有远端任务正常完成 } } return nil }这段代码与设计文档给出的骨架完全一致当前实现位于 pkg/executor/simple.go 的broadcast函数。两个关键参数决定了广播行为SetStoreType(kv.TiDB)保证 coprocessor 请求只发给 TiDB 节点而不是 TiKVSetTiDBServerID(0)server ID 为 0 意味着为每一个 TiDB 节点生成一个 coprocessor 任务从而实现全集群广播。3.3 远端节点的执行路径当某个 TiDB 节点收到 coprocessor 请求后会构造 DAG 执行器把 protobuf 消息转换为真实物理计划——PhysicalSimpleWrapper定义于 pkg/planner/core/common_plans.go。转换过程位于 pkg/planner/core/pb_to_plan.gocase tipb.ExecType_TypeBroadcastQuery: p, err b.pbToBroadcastQuery(e)func (b *PBPlanBuilder) pbToBroadcastQuery(e *tipb.Executor) (base.PhysicalPlan, error) { pa : parser.New() stmt, err : pa.ParseOneStmt(*e.BroadcastQuery.Query, , ) if err ! nil { return nil, errors.Trace(err) } simple : Simple{Statement: stmt, IsFromRemote: true, ResolveCtx: resolve.NewContext()} return PhysicalSimpleWrapper{Inner: simple}, nil }转换时把 protobuf 中的 SQL 文本重新解析为REFRESH STATS语句并设置IsFromRemote: true随后交由 SimpleExec 的executeRefreshStats执行——由于IsFromRemote为真远端节点会直接调用executeRefreshStatsOnCurrentInstance完成本地统计信息刷新而不会再触发二次广播避免循环。3.4 整体工作流整个流程可以概括为六个阶段对应上文的流程示意图客户端mysql 客户端或 BR连接任一 TiDB 节点下发REFRESH STATS ... CLUSTER协调节点解析 SQL、构建PhysicalSimpleWrapper物理计划调用executeRefreshStats协调节点构造TypeBroadcastQueryDAG 请求以StoreTypeTiDB、TiDBServerID0参数生成 KV 请求请求以 coprocessor 请求的形式并行分发给集群中所有 TiDB 节点包括自身各 TiDB 节点解析请求中的 SQL标记IsFromRemotetrue在本地执行统计信息刷新协调节点消费并收集所有远端响应向客户端返回执行结果。这一设计让统计信息刷新无需触达存储层各节点并行处理避免单点瓶颈。四、并发控制、超时与 FAQ4.1 并发控制由于复用了 coprocessor 框架REFRESH STATS无需引入新的并发控制机制直接继承 coprocessor 框架已有的并发控制能力。设计文档给出了两个可选项复用系统变量tidb_distsql_scan_concurrency决定同时处理的任务数量或引入新变量控制一次发出的请求数量。同时设计文档强调必须通过充分测试评估该命令对 TiKV 在线负载的性能影响这也被列入影响与风险章节。4.2 超时控制超时同样复用 coprocessor 框架的能力通过覆盖 pkg/distsql/context/context.go 中的MaxExecutionTime为任务设置自定义超时。默认值为 0即无超时。如需显式指定设计文档给出了参考公式Timeout min( ((Total Tables * Number of Nodes) / 1M tables per minute) 1 minute buffer, 15 minutes )批任务超时按表数量 × 节点数估算公式假设初始化 100 万张表的统计信息大约需要 1 分钟该估算需经测试验证并附加 1 分钟缓冲以容忍轻微延迟为避免过长的等待超时上限封顶15 分钟超过即视为不可接受。4.3 FAQ两个关键问题设计文档明确了两个容易引起困惑的问题如果集群中已存在部分表统计信息物理恢复统计信息会不会把它们全部丢弃答会。物理恢复本质上会重建统计信息表因此在该场景下会回退到统计信息 JSON 备份/恢复方案来规避此问题。如何处理一个 init stats 请求还在处理中又收到另一个请求的情况答为避免实现复杂化应在入口处尽早检查是否已有 init stats 操作正在进行若是则尽早返回错误。五、测试设计、影响与备选方案5.1 测试设计设计文档规划了四类测试功能测试覆盖REFRESH STATS命令的所有 SQL 语法变体验证在分布式环境下多 TiDB 节点上的成功执行验证统计信息被正确刷新与更新覆盖错误处理与边界情况。仓库中已有相关单元测试见 pkg/statistics/handle/handletest/initstats/init_stats_test.go 及 pkg/executor/simple_test.go。场景测试① BR 备份/恢复场景——统计信息已初始化后BR 对统计信息做物理恢复② 调试内存统计信息问题场景——统计信息未初始化用户希望刷新统计信息。兼容性测试本功能不需要兼容性测试。性能测试① 测量统计信息刷新过程中对 TiKV 在线负载的影响② 评估大规模表集上的统计信息初始化效率。测试结果将用于确定并发限制与超时设置的合理默认值。5.2 影响与风险该功能可能对 TiKV 的在线负载产生性能影响需要通过全面测试充分评估。5.3 备选方案Investigation Alternatives设计文档记录的备选方案是让用户全量重启 TiDB 节点利用启动过程再次触发统计信息初始化。该方案可作为REFRESH STATS未可用时的降级手段但正如动机部分所述重启对用户并不友好。六、待解决问题Unresolved Questions设计文档在定稿时仍有两个开放问题刷新统计信息时对 TiKV 在线负载的性能影响量级是多少在确定基于集群拓扑的合适超时值时应考虑哪些因素这两个问题也直接关联到上文 4.1 并发控制与 4.2 超时公式中需经测试验证的估算项是后续实现与压测需要重点回答的内容。总结REFRESH STATS命令的设计精髓在于复用而非重建语法层复用现有SimpleExec执行框架跨节点分发复用 coprocessor 广播能力KILL语句同款机制并发与超时控制也直接继承 distsql 框架的既有能力而基础设施的通用性使其未来可平滑复用到 plan cache 跨节点清理等场景。从当前仓库源码看executeRefreshStats、broadcast、pbToBroadcastQuery等关键函数已在 pkg/executor/simple.go 与 pkg/planner/core/pb_to_plan.go 中实现落地。对于 BR 物理恢复统计信息后的热加载、以及内存统计信息异常的在线排查该命令提供了无需重启、无需 ANALYZE 的标准化解决方案。【免费下载链接】tidbTiDB is built for agentic workloads that grow unpredictably, with ACID guarantees and native support for transactions, analytics, and vector search. No data silos. No noisy neighbors. No infrastructure ceiling.项目地址: https://gitcode.com/GitHub_Trending/ti/tidb创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考