DMDPC 学习:架构、部署、运维与调优

速览与阅读地图

适用范围与验证提示:本文按 2026-07-16 可访问的达梦官方 DMDPC 文档整理,适合作为原理学习与方案设计参考。参数默认值、过程签名、错误码和部署限制会随 DM8 版本、补丁级别、操作系统及单/多副本、BS 模式而变化;所有部署、切换和恢复命令均应先在与生产同版本的测试环境验证。官方网页会动态更新,文中"当前""默认"等描述均以该访问日期为准。

本文先用本节建立全景,再按以下顺序展开细节:

  1. 对象关系与数据分布;
  2. 分布式执行、事务与 RAFT;
  3. 单副本与多副本部署、SP 接入;
  4. 备份恢复、日常运维、扩容迁移与性能调优。

代码约定 :代码块中出现 <...> 的内容必须替换为实际值;出现 ... 的块仅用于说明语法结构,不能直接执行。涉及路径的命令默认采用 Linux 风格;若使用 Windows,应按实际目录、服务管理方式和安装路径改写。

DMDPC全称 DM Distributed Processing Cluster,是基于DM8发展而来的分布式数据库系统,同时支持OLTP与OLAP,核心目标是:

  • 将数据分散存储到多个节点;
  • 将SQL拆成多个子任务并行执行;
  • 通过增加节点扩展计算或存储能力;
  • 通过RAFT多副本保障元数据和业务数据的高可用;
  • 尽可能保留DM8单机的SQL、事务和应用兼容能力。

它不是简单地把多台DM8主备库组合起来,而是重新划分了数据库的职责。达梦官方DMDPC概述
#mermaid-svg-bgzWSIXELtkyWNh4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-bgzWSIXELtkyWNh4 .error-icon{fill:#552222;}#mermaid-svg-bgzWSIXELtkyWNh4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-bgzWSIXELtkyWNh4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-bgzWSIXELtkyWNh4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-bgzWSIXELtkyWNh4 .marker.cross{stroke:#333333;}#mermaid-svg-bgzWSIXELtkyWNh4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-bgzWSIXELtkyWNh4 p{margin:0;}#mermaid-svg-bgzWSIXELtkyWNh4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster-label text{fill:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster-label span{color:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster-label span p{background-color:transparent;}#mermaid-svg-bgzWSIXELtkyWNh4 .label text,#mermaid-svg-bgzWSIXELtkyWNh4 span{fill:#333;color:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 .node rect,#mermaid-svg-bgzWSIXELtkyWNh4 .node circle,#mermaid-svg-bgzWSIXELtkyWNh4 .node ellipse,#mermaid-svg-bgzWSIXELtkyWNh4 .node polygon,#mermaid-svg-bgzWSIXELtkyWNh4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-bgzWSIXELtkyWNh4 .rough-node .label text,#mermaid-svg-bgzWSIXELtkyWNh4 .node .label text,#mermaid-svg-bgzWSIXELtkyWNh4 .image-shape .label,#mermaid-svg-bgzWSIXELtkyWNh4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-bgzWSIXELtkyWNh4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-bgzWSIXELtkyWNh4 .rough-node .label,#mermaid-svg-bgzWSIXELtkyWNh4 .node .label,#mermaid-svg-bgzWSIXELtkyWNh4 .image-shape .label,#mermaid-svg-bgzWSIXELtkyWNh4 .icon-shape .label{text-align:center;}#mermaid-svg-bgzWSIXELtkyWNh4 .node.clickable{cursor:pointer;}#mermaid-svg-bgzWSIXELtkyWNh4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-bgzWSIXELtkyWNh4 .arrowheadPath{fill:#333333;}#mermaid-svg-bgzWSIXELtkyWNh4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-bgzWSIXELtkyWNh4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-bgzWSIXELtkyWNh4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bgzWSIXELtkyWNh4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-bgzWSIXELtkyWNh4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bgzWSIXELtkyWNh4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster text{fill:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 .cluster span{color:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-bgzWSIXELtkyWNh4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-bgzWSIXELtkyWNh4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-bgzWSIXELtkyWNh4 .icon-shape,#mermaid-svg-bgzWSIXELtkyWNh4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-bgzWSIXELtkyWNh4 .icon-shape p,#mermaid-svg-bgzWSIXELtkyWNh4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-bgzWSIXELtkyWNh4 .icon-shape .label rect,#mermaid-svg-bgzWSIXELtkyWNh4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-bgzWSIXELtkyWNh4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-bgzWSIXELtkyWNh4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-bgzWSIXELtkyWNh4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用或客户端
SP:接入、优化、调度
MP:元数据服务
BP RAFT组1
BP RAFT组2
主副本+备副本
主副本+备副本

1. SP:计划生成节点

SP相当于集群的SQL入口和调度中心,主要负责:

  • 接收客户端连接;
  • 解析、优化SQL;
  • 生成分布式执行计划;
  • 使用ESEND、ERECV等通信操作符划分子计划;
  • 计算各子计划的并行度;
  • 将子任务调度到相关BP或其他SP;
  • 汇总结果并返回客户端。

SP不保存用户数据,也不持久化元数据,因此通常被称为无状态计算节点。一个集群可以部署多个SP,应用可连接任意SP获得完整数据库服务。

SP发生故障时,不涉及数据恢复,主要解决客户端连接和正在执行任务的迁移或重试问题。因此SP层高可用依赖:

  • 部署多个SP;
  • 客户端服务名配置多个SP地址;
  • 驱动或连接池执行故障转移;
  • 必要时设置SP组,实现不同业务间的计算资源隔离。

需要注意:SP"无状态"不等于运行时没有缓存和会话状态,而是指它不承担用户数据和数据字典的持久化存储。

2. MP:元数据服务器节点

MP负责整个集群的数据字典和集群配置,是DMDPC的元数据中心,保存的内容包括:

  • 表、视图、索引等对象定义;
  • BP、SP及RAFT组的注册信息;
  • BP组、SP组、表空间组等集群信息;
  • 分区与存储位置的对应关系;
  • 分布式事务需要的部分全局信息。

所有DDL请求都要经过SP转发给MP执行。SP和BP需要字典对象时,先查本地字典缓存;缓存不存在,再向MP请求。

逻辑上,一个DMDPC集群只有一套MP元数据服务;为了避免单点故障,这套MP可以部署成一个RAFT多副本系统。因此"只有一个MP"指只有一套元数据,而不是生产环境只能有一个MP实例。DMDPC关键技术:元数据服务

3. BP:数据存储节点

BP是真正保存用户数据的节点,主要负责:

  • 保存表空间、数据文件、日志等;
  • 接收SP下发的子任务;
  • 在本地执行扫描、过滤、连接、聚合等操作;
  • 将中间结果或最终结果返回SP;
  • 通过增加或迁移BP实现存储扩展和负载调整。

DMDPC的数据分布以分区为基本单位:

  • 一个分区只能位于一个BP RAFT组;
  • 同一张表的不同分区可以分布在不同BP上;
  • 非分区表只有一个分区,因此只能存放在一个BP上;
  • DMDPC中,分区列同时也是分布列;
  • 这点与DM MPP中"分区"和"分布"是两个概念不同。

因此,在DMDPC中,表的分区设计不仅影响单表访问效率,也直接决定数据落在哪些BP、SQL能否并行以及节点间需要传输多少数据。DMDPC使用规范

一条SQL如何执行

以客户端连接SP执行查询为例:

  1. SP接收SQL并进行语法、语义处理。
  2. SP从本地缓存或MP获取相关表、索引和分区元数据。
  3. 优化器根据统计信息、数据分布和分区位置生成分布式计划。
  4. SP以ESEND、ERECV等通信操作符为边界,将计划拆成多个子计划。
  5. QC负责整个查询的调度。
  6. 各BP上的SQC负责本节点子任务的执行协调。
  7. BP扫描本地数据,尽量在本地完成过滤、投影和部分聚合。
  8. BP、SP之间按照广播、哈希重分布、定向发送等方式交换数据。
  9. SP完成最终汇总并向客户端返回结果。

DMDPC采用生产者---消费者执行模型。下层子任务产生数据,上层子任务消费数据,两者可以形成流水线并行执行,而不必等下层全部执行完再启动上层。DMDPC子计划与执行模型

高可用的三个层次

SP层高可用

SP没有数据副本问题,主要通过多个SP和客户端故障转移实现。

SP故障通常造成:

  • 当前连接中断;
  • 当前正在执行的事务或SQL失败;
  • 应用切换到其他SP后重新建立连接;
  • 未完成事务需要根据应用策略重试。

MP层高可用

MP可以部署为由奇数个实例组成的RAFT组,常见为3副本:

  • 一个Leader作为主库;
  • 其余实例为Follower;
  • 主节点故障后,满足选举条件的节点重新选举Leader;
  • 新Leader自动切换为主库并继续提供元数据服务。

MP保存的是整个集群唯一的一套元数据,因此MP故障可能影响:

  • DDL操作;
  • 字典对象加载;
  • 节点注册及集群管理;
  • 部分依赖MP的全局协调操作。

生产环境不宜使用单副本MP。

BP层高可用

每个BP数据分片可以分别组成RAFT组。例如:

  • RAFT_BP_01保存第一组数据;
  • RAFT_BP_02保存第二组数据;
  • 每个RAFT组内部有一个Leader和多个Follower;
  • 同一RAFT组中的实例保存相同数据;
  • 不同RAFT组保存不同的数据分片。

BP主节点故障后,只在该RAFT组内重新选主,不需要整个集群统一切换。

这里要避免一个常见误解:白皮书中的"异地多活灾备"不代表同一RAFT组的多个副本可以同时对外写入。同一RAFT组在正常情况下仍然只有一个Leader负责对外服务,其他副本用于一致性复制和故障接管。

RAFT组成员必须为奇数,官方当前指标显示最大副本数为9。多副本能否继续服务,取决于RAFT多数派是否仍然存在。DMDPC基本概念和技术指标

分布式事务

单机DM8中,一个事务只在一个实例内完成;DMDPC中,一个事务可能同时修改多个BP上的数据,因此必须解决:

  • 多节点提交或回滚的一致性;
  • 不同节点事务状态的统一判断;
  • 跨节点数据可见性;
  • 节点故障时未完成事务的处理。

DMDPC通过分布式事务协调、全局时钟、提交信息和多版本可见性判断等机制保证事务一致性。配置中还涉及DPC_2PC等参数。关闭两阶段提交后,部分功能会受到限制,例如官方明确指出不能使用XA接口。

需要将两个概念分开:

  • RAFT解决同一数据分片多个副本之间的日志一致性和主节点选举;
  • 分布式事务解决一个业务事务跨越多个BP数据分片时的原子提交问题。

这两者共同构成DMDPC的数据一致性体系,但解决的问题不同。

部署形态

最小实验集群只需要:

  • 1个SP;
  • 1个MP;
  • 1个BP。

但这种结构只能验证基本功能,看不出完整的分布式计划、跨节点数据交换和高可用机制。官方也建议至少配置两个BP,以便观察不同子计划和调度形态。DMDPC集群部署

适合后续学习的实验拓扑是:

  • 2个SP;
  • 1组3副本MP;
  • 2组BP,每组先做单副本,再逐步扩展为3副本。

这样可以依次完成:

  • 分布式数据存储实验;
  • 跨BP查询和执行计划观察;
  • SP连接切换;
  • MP主节点故障选举;
  • BP主节点故障选举;
  • 节点动态增加和删除;
  • 数据迁移与表空间迁移;
  • 多副本网络隔离和多数派实验。

与DM8单机最重要的区别

维度 DM8单机 DMDPC
SQL入口 当前数据库实例 SP或BS
元数据 本实例系统表 MP集中管理
用户数据 本实例数据文件 分布在多个BP
执行计划 单实例计划 分布式计划与子计划
并行方式 实例内线程并行 多实例并行+实例内并行
事务 本地事务 可能跨多个BP
高可用 通常依赖数据守护等 MP/BP内置RAFT多副本
扩展方式 主要纵向扩展 SP计算扩展、BP存储扩展
故障范围 整个实例 按SP或RAFT组隔离
核心设计点 表、索引、表空间 还要考虑分区、数据位置和网络交换

后续逐步深入的顺序为:

  1. SP、BP、MP、BS及各类组的关系
  2. 数据如何分布到BP
  3. 分布式执行计划与数据交换
  4. 分布式事务和数据可见性
  5. RAFT多副本、选主与故障恢复
  6. 单副本集群部署
  7. MP、BP多副本部署
  8. 日常启停、巡检、扩缩容与迁移
  9. 备份还原、升级与容灾演练
  10. SQL诊断、监控视图与性能调优

第1部分:DMDPC的对象关系

先抓住一句话:SP、BP、MP是"节点角色";RAFT组解决"同一份数据的副本一致性";BP组、SP组、表空间组是"逻辑资源集合";域表示"物理部署位置"。

它们不是同一层级,不能混着理解。

层级 对象 作用 是否保存用户数据
节点角色 SP 接入SQL、生成和调度分布式计划
节点角色 BP 保存数据、执行子任务
节点角色 MP 保存元数据、提供字典服务 不保存用户业务数据
副本层 RAFT组 同一份数据的主备副本集合 BP/MP保存相同副本数据
逻辑资源层 BP组 一组BP RAFT组的集合,便于指定数据存储范围 间接关联
逻辑资源层 SP组 一组SP的集合,用于计算资源隔离
存储逻辑层 表空间组 一组表空间的集合,便于建表或设置用户默认存储位置 间接关联
物理位置层 MP域、BP域 标记节点所在机房、城市或故障域

1. SP、BP、MP:三个真正的节点角色

SP(SQL Processor)是客户端连接的入口。它接收SQL、查元数据、生成执行计划、将计划拆成子计划、计算并行度、调度BP或其他SP执行,最后汇总结果返回应用。

BP(Backend Processor)是实际存储数据和执行数据访问任务的节点。表数据、索引、表空间、日志等都由BP承担;SP下发子任务后,BP负责扫描、过滤、连接、聚合等计算并返回结果。

MP(Metadata Processor)是全局元数据中心。表定义、索引定义、节点注册信息、分区位置等由MP统一管理。DDL由SP转发到MP执行;SP、BP需要字典对象时,也会向MP获取或更新缓存。官方架构说明

可以先将它理解为:
#mermaid-svg-1Yv0B01SEBlD6T6S{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-1Yv0B01SEBlD6T6S .error-icon{fill:#552222;}#mermaid-svg-1Yv0B01SEBlD6T6S .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-1Yv0B01SEBlD6T6S .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-1Yv0B01SEBlD6T6S .marker{fill:#333333;stroke:#333333;}#mermaid-svg-1Yv0B01SEBlD6T6S .marker.cross{stroke:#333333;}#mermaid-svg-1Yv0B01SEBlD6T6S svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-1Yv0B01SEBlD6T6S p{margin:0;}#mermaid-svg-1Yv0B01SEBlD6T6S .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster-label text{fill:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster-label span{color:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster-label span p{background-color:transparent;}#mermaid-svg-1Yv0B01SEBlD6T6S .label text,#mermaid-svg-1Yv0B01SEBlD6T6S span{fill:#333;color:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S .node rect,#mermaid-svg-1Yv0B01SEBlD6T6S .node circle,#mermaid-svg-1Yv0B01SEBlD6T6S .node ellipse,#mermaid-svg-1Yv0B01SEBlD6T6S .node polygon,#mermaid-svg-1Yv0B01SEBlD6T6S .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-1Yv0B01SEBlD6T6S .rough-node .label text,#mermaid-svg-1Yv0B01SEBlD6T6S .node .label text,#mermaid-svg-1Yv0B01SEBlD6T6S .image-shape .label,#mermaid-svg-1Yv0B01SEBlD6T6S .icon-shape .label{text-anchor:middle;}#mermaid-svg-1Yv0B01SEBlD6T6S .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-1Yv0B01SEBlD6T6S .rough-node .label,#mermaid-svg-1Yv0B01SEBlD6T6S .node .label,#mermaid-svg-1Yv0B01SEBlD6T6S .image-shape .label,#mermaid-svg-1Yv0B01SEBlD6T6S .icon-shape .label{text-align:center;}#mermaid-svg-1Yv0B01SEBlD6T6S .node.clickable{cursor:pointer;}#mermaid-svg-1Yv0B01SEBlD6T6S .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-1Yv0B01SEBlD6T6S .arrowheadPath{fill:#333333;}#mermaid-svg-1Yv0B01SEBlD6T6S .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-1Yv0B01SEBlD6T6S .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-1Yv0B01SEBlD6T6S .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1Yv0B01SEBlD6T6S .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-1Yv0B01SEBlD6T6S .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1Yv0B01SEBlD6T6S .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster text{fill:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S .cluster span{color:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-1Yv0B01SEBlD6T6S .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-1Yv0B01SEBlD6T6S rect.text{fill:none;stroke-width:0;}#mermaid-svg-1Yv0B01SEBlD6T6S .icon-shape,#mermaid-svg-1Yv0B01SEBlD6T6S .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-1Yv0B01SEBlD6T6S .icon-shape p,#mermaid-svg-1Yv0B01SEBlD6T6S .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-1Yv0B01SEBlD6T6S .icon-shape .label rect,#mermaid-svg-1Yv0B01SEBlD6T6S .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-1Yv0B01SEBlD6T6S .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-1Yv0B01SEBlD6T6S .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-1Yv0B01SEBlD6T6S :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用
SP:接入、优化、调度
MP:元数据
BP:数据分片1
BP:数据分片2

单机DM8中,这三类职责主要集中在一个实例内;DMDPC将其拆开,形成"计算、存储、元数据"分工。

2. BS:不是第四种节点,而是BP的直连运行模式

BP有两种运行方式:

  • BP模式:纯后台存储节点,通常不对用户直接提供连接服务。
  • BS模式:直连BP模式。它仍然是BP,但同时具备SP生成分布式计划和执行部分计算的能力,用户可以直接登录。

如果业务访问的数据刚好都在这个BP本地,BS可避免"客户端 → SP → BP"的额外网络交互,性能会更接近单机访问;一旦需要访问其他BP,BS仍会与MP、其他BP及辅助SP通信。

因此,BS不是取代SP的常规生产架构,而是BP兼任前端能力的一种模式 。官方明确规定,对外提供服务的节点为SP和BS模式的BP。官方基本概念

3. RAFT组:同一份数据的副本集合

RAFT组是高可用的核心单位。

例如BP_RAFT_1由三个BP实例组成:

text 复制代码
BP1(Leader/主) + BP1_R1(Follower/备) + BP1_R2(Follower/备)

这三个普通副本保存的是同一份数据。正常时只有Leader作为主节点提供服务;主节点故障后,剩余成员通过RAFT选举出新Leader。影子库是特殊成员,不保存完整可服务数据,后文单独说明。

要点:

  • 一个DMDPC集群可以配置多个BP RAFT组,每个RAFT组对应不同的数据分片。
  • MP最多只有一个RAFT组,通常称为MP_RAFT
  • 多副本RAFT组的成员数应为奇数;当前官方技术指标中,最大副本数为9。
  • 单副本也属于RAFT组,只是组内只有一个实例。
  • 新增多副本节点时,会先以LEARNER身份同步数据,完成后再成为正式成员并参与选举。
  • SP也会注册到"SP类型RAFT组"中,但这只是统一管理方式;SP不存数据,也不构成数据副本关系。一个SP类型RAFT组最多加入一个SP节点。

所以,**RAFT组不是"一个节点组"的泛称,而是数据副本和主备切换的边界。**一个BP RAFT组故障或切换,不代表所有BP都一起切换。官方RAFT组定义

4. BP组:指定"数据可以放到哪些存储分片"

BP组是多个BP RAFT组的逻辑集合。

例如:

text 复制代码
BP组 BG_ORDER = { BP_RAFT_1, BP_RAFT_2, BP_RAFT_3 }

它的作用不是复制数据,而是简化存储位置的指定。创建分区表或表空间时,指定BG_ORDER,系统会在该组包含的RAFT组中选择位置;表的不同分区可分散到不同RAFT组中。

要注意两点:

  • BP组可以只是全部BP RAFT组的一个子集,也可以为空。
  • 同一个BP RAFT组可以同时属于多个BP组。

这意味着BP组更像"存储资源池"或"可选节点范围",不是高可用组。真正负责副本一致性的是RAFT组。官方BP组说明

5. SP组:隔离计算资源,而非高可用副本

SP组也是逻辑集合,成员是SP类型RAFT组。

例如:

text 复制代码
SP组 SPG_OLTP = { SP1, SP2 }
SP组 SPG_ANALYTICS = { SP3, SP4 }

它的价值是把不同应用或工作负载使用的计算节点分开:

  • 在线交易应用用SPG_OLTP
  • 报表和分析应用用SPG_ANALYTICS
  • 避免重型分析SQL长期占用交易业务的计算资源。

它会影响计划生成与调度时可使用哪些SP,但不影响数据存放位置,也不等同于数据副本。

6. 域:为容灾设计的物理位置概念

域描述"节点部署在哪里",常对应:

  • 一台服务器;
  • 一个机架;
  • 一个机房;
  • 一个城市或数据中心。

DMDPC区分MP域和BP域:

  • 一个MP域最多包含一个MP实例;
  • 一个BP域可包含多个BP实例,且这些BP可属于不同RAFT组;
  • 同一RAFT组的副本应该跨不同域部署,避免一个机房、服务器或地域故障同时摧毁全部副本。

例如三副本BP_RAFT_1

text 复制代码
北京BP域:BP1
上海BP域:BP1_R1
武汉BP域:BP1_R2

这里的"跨域"才让三副本真正具备机房级或地域级容灾意义。三副本若都部署在同一台服务器,逻辑上有副本,物理上仍是单点风险。

7. 表空间与表空间组:把逻辑存储映射到BP

表空间仍然保留DM8的基本含义:对象逻辑上存放在表空间中,物理上落到数据文件。

但在DMDPC中创建表空间时,可以通过STORAGE指定其存储位置:

sql 复制代码
CREATE TABLESPACE TS_ORDER
DATAFILE 'TS_ORDER.DBF' SIZE 128
STORAGE(ON BP_RAFT_1);

也可以指定BP组:

sql 复制代码
CREATE TABLESPACE TS_ORDER
DATAFILE 'TS_ORDER.DBF' SIZE 128
STORAGE(ON BG_ORDER);

前者明确落在某个RAFT组;后者由系统从BP组内选择一个RAFT组。若不指定,系统会从现有BP RAFT组中选择。

表空间组则是多个表空间的逻辑集合,可设置为用户的默认表空间组,适合需要统一管理多个存储位置的场景。它不是副本机制,也不直接等于一个BP组。官方表空间管理说明

用一个完整例子串起来

假设有:

text 复制代码
MP_RAFT = {MP1, MP2, MP3}

BP_RAFT_A = {BP1, BP1_R1, BP1_R2}
BP_RAFT_B = {BP2, BP2_R1, BP2_R2}

BP组 BG_BIZ = {BP_RAFT_A, BP_RAFT_B}

SP组 SPG_APP = {SP1, SP2}

此时:

  • MP1/MP2/MP3:保存同一份全局元数据;
  • BP_RAFT_ABP_RAFT_B:保存两份不同的业务数据分片;
  • 每个BP RAFT组内部才有主备复制和故障选主;
  • BG_BIZ:指定业务表数据可以分布在A、B两个存储分片上;
  • SP1/ SP2:为应用提供SQL接入与计算调度;
  • BP1故障,只影响BP_RAFT_A的主节点,BP1_R1BP1_R2会接管;
  • SP1故障,应用可连接SP2,但正在SP1上执行的会话和事务仍需应用重连或重试。

最后记住这个判断口诀:

节点角色看"做什么";RAFT组看"谁保存同一份数据";BP/SP组看"资源怎么划分";域看"副本分散在哪里";表空间组看"对象默认放到哪一类存储"。

第2部分:数据如何分布到BP

DMDPC里,数据不是直接"放到某个BP",而是按下面这条链路落盘:
#mermaid-svg-eX9WuUIX5YHiWpsv{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-eX9WuUIX5YHiWpsv .error-icon{fill:#552222;}#mermaid-svg-eX9WuUIX5YHiWpsv .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-eX9WuUIX5YHiWpsv .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-eX9WuUIX5YHiWpsv .marker{fill:#333333;stroke:#333333;}#mermaid-svg-eX9WuUIX5YHiWpsv .marker.cross{stroke:#333333;}#mermaid-svg-eX9WuUIX5YHiWpsv svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-eX9WuUIX5YHiWpsv p{margin:0;}#mermaid-svg-eX9WuUIX5YHiWpsv .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster-label text{fill:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster-label span{color:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster-label span p{background-color:transparent;}#mermaid-svg-eX9WuUIX5YHiWpsv .label text,#mermaid-svg-eX9WuUIX5YHiWpsv span{fill:#333;color:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv .node rect,#mermaid-svg-eX9WuUIX5YHiWpsv .node circle,#mermaid-svg-eX9WuUIX5YHiWpsv .node ellipse,#mermaid-svg-eX9WuUIX5YHiWpsv .node polygon,#mermaid-svg-eX9WuUIX5YHiWpsv .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-eX9WuUIX5YHiWpsv .rough-node .label text,#mermaid-svg-eX9WuUIX5YHiWpsv .node .label text,#mermaid-svg-eX9WuUIX5YHiWpsv .image-shape .label,#mermaid-svg-eX9WuUIX5YHiWpsv .icon-shape .label{text-anchor:middle;}#mermaid-svg-eX9WuUIX5YHiWpsv .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-eX9WuUIX5YHiWpsv .rough-node .label,#mermaid-svg-eX9WuUIX5YHiWpsv .node .label,#mermaid-svg-eX9WuUIX5YHiWpsv .image-shape .label,#mermaid-svg-eX9WuUIX5YHiWpsv .icon-shape .label{text-align:center;}#mermaid-svg-eX9WuUIX5YHiWpsv .node.clickable{cursor:pointer;}#mermaid-svg-eX9WuUIX5YHiWpsv .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-eX9WuUIX5YHiWpsv .arrowheadPath{fill:#333333;}#mermaid-svg-eX9WuUIX5YHiWpsv .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-eX9WuUIX5YHiWpsv .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-eX9WuUIX5YHiWpsv .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eX9WuUIX5YHiWpsv .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-eX9WuUIX5YHiWpsv .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eX9WuUIX5YHiWpsv .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster text{fill:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv .cluster span{color:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-eX9WuUIX5YHiWpsv .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-eX9WuUIX5YHiWpsv rect.text{fill:none;stroke-width:0;}#mermaid-svg-eX9WuUIX5YHiWpsv .icon-shape,#mermaid-svg-eX9WuUIX5YHiWpsv .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-eX9WuUIX5YHiWpsv .icon-shape p,#mermaid-svg-eX9WuUIX5YHiWpsv .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-eX9WuUIX5YHiWpsv .icon-shape .label rect,#mermaid-svg-eX9WuUIX5YHiWpsv .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-eX9WuUIX5YHiWpsv .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-eX9WuUIX5YHiWpsv .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-eX9WuUIX5YHiWpsv :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 表
分区 / 子表
表空间
BP RAFT组
主BP
备BP副本

也就是说:

表的每个子表先确定使用哪个表空间;表空间再确定属于哪个BP RAFT组;该RAFT组的主、备BP共同保存这个子表的数据。

因此,DMDPC中的分区不只是为了管理大表,也决定了数据分布和计算位置。对二级分区表而言,实际的存储与分布单位是叶子子分区(叶子子表)。

1. 最核心的规则:子表就是分布单位

DMDPC以子表为数据分布的基本单位:

  • 同一个子表只能位于一个BP RAFT组;
  • 同一张表的不同子表可以分布在不同BP RAFT组;
  • 一个BP RAFT组可保存多个子表;
  • 非分区表只有一个子表,因此只能落在一个BP RAFT组;
  • 在DMDPC中,分区列同时也是分布列。

这与DM MPP不同:MPP中"分区"和"分布"是两个概念;DMDPC中二者合一。官方使用规范

例如订单表按ORDER_ID哈希分为4个分区:

text 复制代码
P1 → TS_ORDER_1 → BP_RAFT_1
P2 → TS_ORDER_2 → BP_RAFT_2
P3 → TS_ORDER_3 → BP_RAFT_1
P4 → TS_ORDER_4 → BP_RAFT_2

数据插入时,系统根据ORDER_ID计算应进入哪个分区;分区所在的RAFT组,就是这行数据的存储和主要执行位置。

2. 三种常见分区方式

分区方式 数据进入规则 更适合的场景 主要风险
RANGE范围分区 按时间、数值区间划分 日志、订单、流水、历史数据 热点时间段可能集中到少数BP
HASH哈希分区 按分区键哈希值均匀分布 高并发交易、按主键访问、关联查询 不利于按时间范围清理历史数据
LIST列表分区 按枚举值分配 省份、区域、业务线、租户类别 值分布不均时容易倾斜

例如:

  • ORDER_TIME按月做RANGE分区,方便按月查询、归档和清理;
  • ORDER_ID按HASH分区,数据通常更均衡;
  • REGION_CODE按LIST分区,便于按区域隔离数据。

选分区键时不能只看查询条件,还要同时看:

  • 数据是否均匀;
  • 是否会形成热点BP;
  • 主要关联条件是什么;
  • 是否需要按时间快速维护历史数据;
  • 未来是否要扩容、迁移或增加分区。

3. 表空间:分区真正落到哪一组BP的桥梁

在DMDPC中,用户表空间实际建在BP上;MP只管理表空间元数据。MP包含系统类表空间,SP主要包含临时表空间,BP才包含用户表空间和用户数据。官方数据存储说明

创建表空间时,可以明确指定它属于哪个BP RAFT组:

sql 复制代码
CREATE TABLESPACE TS_1
DATAFILE 'TS_1.DBF' SIZE 128
STORAGE(ON RAFT_1);

也可以指定BP组:

sql 复制代码
CREATE TABLESPACE TS_BIZ
DATAFILE 'TS_BIZ.DBF' SIZE 128
STORAGE(ON BG_BIZ);

两者区别:

  • 指定RAFT_1:数据位置固定,表空间明确落在这一BP RAFT组;该组的普通副本各自保存对应物理文件;
  • 指定BG_BIZ:系统从该BP组包含的RAFT组中选择一个位置;
  • 不指定:系统从可用BP RAFT组中选择。

因此,BP组决定"可选存储范围",表空间决定"实际存储落点"。 官方表空间管理说明

4. 分区组:让关联表天然对齐

分区组(PG)是DMDPC中非常重要但容易被忽略的概念。

它不是存放数据的对象,而是一份可复用的分区定义。它定义分区方式、数量、边界和字段类型;若在分区组中指定STORAGE,还会列出各子表使用的表空间。表空间再映射到BP RAFT组。多个表使用同一个分区组后:

  • 分区方式一致;
  • 分区数量和边界一致;
  • 对应分区进入相同表空间;
  • 因而落在相同BP RAFT组。

例如订单表和订单明细表,常按ORDER_ID建立相同的哈希分区组:

text 复制代码
PG_ORDER:
P1 → TS_1 → BP_RAFT_1
P2 → TS_2 → BP_RAFT_2
P3 → TS_3 → BP_RAFT_3
P4 → TS_4 → BP_RAFT_4

T_ORDERT_ORDER_ITEM都使用PG_ORDER后,两个表中ORDER_ID相同的数据会在对应分区中对齐。之后按ORDER_ID关联时,优化器有机会在本地完成更多连接操作,减少跨BP数据交换和跨机事务开销。

官方明确建议:需要经常连接的表,应尽量使用相同分区组;这既简化建表,也可改善连接和事务性能。官方分区组说明

示意写法如下:

sql 复制代码
CREATE PARTITION GROUP PG_ORDER
PARTITION BY HASH(INT) PARTITIONS 4
STORAGE(ON TS_1, TS_2, TS_3, TS_4);

CREATE TABLE T_ORDER (
    ORDER_ID INT NOT NULL,
    ORDER_TIME DATETIME,
    STATUS VARCHAR(16)
) USING PARTITION GROUP PG_ORDER BY (ORDER_ID);

CREATE TABLE T_ORDER_ITEM (
    ITEM_ID BIGINT NOT NULL,
    ORDER_ID INT NOT NULL,
    SKU_ID BIGINT,
    QUANTITY INT
) USING PARTITION GROUP PG_ORDER BY (ORDER_ID);

使用分区组建表时,表上的分区列数量和数据类型必须与分区组定义完全匹配。

5. 本地连接与跨BP连接的区别

假设T_ORDERT_ORDER_ITEM都按ORDER_ID、相同分区规则、相同存储位置分布:

sql 复制代码
SELECT *
FROM T_ORDER O
JOIN T_ORDER_ITEM I ON O.ORDER_ID = I.ORDER_ID;

理想情况下:

text 复制代码
BP_RAFT_1:订单P1 与 明细P1本地连接
BP_RAFT_2:订单P2 与 明细P2本地连接
BP_RAFT_3:订单P3 与 明细P3本地连接
......
SP:汇总各BP结果

这类思路就是分区智能连接(PWJ)的基础。

反过来,如果两张表的分区规则、分区数、分区键或存储位置不一致,可能发生:

text 复制代码
BP1扫描T_ORDER
BP2扫描T_ORDER_ITEM
两边把数据重新分发到指定节点
再执行连接

网络传输量、CPU、内存和查询延迟都会上升。DMDPC会用ESENDERECV完成节点间的数据交换,支持哈希、范围、列表、广播、直接发送等多种分发方式。官方数据交换机制

这里的经验很实用:

  • 大表和大表连接:优先考虑让关联键成为一致的分区键;
  • 小表和大表连接:有时广播小表比重分布大表更划算;
  • 两边数据量接近、分布不一致:可能需要两边重新分发;
  • 不要一开始就强行加HINT,先保证统计信息和表设计合理。

6. 数据倾斜:分布式系统最常见的性能陷阱

即使分区数量很多,也不代表负载一定均衡。

例如按REGION_CODE列表分区:

text 复制代码
华东:60%数据
华南:25%数据
其他区域:15%数据

若每个区域放在不同BP,华东所在BP会先成为瓶颈。又如用递增时间列做范围分区,最新分区可能承担绝大部分写入,形成明显写热点。

建表前要重点检查:

  • 分区键基数是否足够;
  • 是否存在极端高频值;
  • 最近数据是否明显更热;
  • 分区数是否与BP数量、预期并行度相匹配;
  • 高并发写入是否集中在最后一个范围分区;
  • 同一业务是否需要按不同维度查询和关联。

在交易型场景中,HASH(业务主键)通常更均衡;在时间序列场景中,常采用"一级按时间范围、二级按业务键哈希"的组合,以兼顾生命周期管理与负载均衡。

7. 扩容后,旧数据不会自动均匀重排

新增BP后,后续创建的表空间、表或分区可以使用新BP;但已经落在旧BP上的数据不会天然自动均衡过去。

DMDPC支持通过表空间迁移实现节点间数据迁移和重分布,例如:

sql 复制代码
ALTER TABLESPACE TS_01 MOVE TO RAFT_2;

迁移既可以采用只读方式,也可以采用读写方式;读写方式在大部分迁移期间允许写入,只在最终切换的短时间内阻塞写操作,但要求MP和BP配置归档。官方表空间迁移说明

所以,扩容规划不能只考虑"加一个BP",还要同时考虑:

  • 新表和新分区怎样落到新BP;
  • 历史热点表空间是否需要迁移;
  • 多副本RAFT组是否跨故障域部署;
  • 迁移时业务允许的读写影响窗口。

这一部分最该记住的四句话

  1. DMDPC中,分区就是数据分布单位
  2. 表空间决定一个分区最终属于哪个BP RAFT组
  3. 经常连接的表,应尽量按关联键使用同一个分区组
  4. 扩容新增BP后,历史数据通常需要通过表空间迁移或重分布规划才能真正均衡。

第3部分:DMDPC如何执行一条SQL

单机DM8里,一条SQL通常由当前实例完成:解析、优化、访问数据、计算、返回结果。

DMDPC里,客户端虽然只连接一个SP,但这条SQL可能同时使用多个BP、多个SP和大量线程。核心变化是:
单机执行计划先生成,再按需要插入节点间的数据交换操作,最后被拆成多个可独立调度的子计划。
#mermaid-svg-w0MKYFQYylZ0zynE{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-w0MKYFQYylZ0zynE .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-w0MKYFQYylZ0zynE .error-icon{fill:#552222;}#mermaid-svg-w0MKYFQYylZ0zynE .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-w0MKYFQYylZ0zynE .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-w0MKYFQYylZ0zynE .marker{fill:#333333;stroke:#333333;}#mermaid-svg-w0MKYFQYylZ0zynE .marker.cross{stroke:#333333;}#mermaid-svg-w0MKYFQYylZ0zynE svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-w0MKYFQYylZ0zynE p{margin:0;}#mermaid-svg-w0MKYFQYylZ0zynE .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-w0MKYFQYylZ0zynE .cluster-label text{fill:#333;}#mermaid-svg-w0MKYFQYylZ0zynE .cluster-label span{color:#333;}#mermaid-svg-w0MKYFQYylZ0zynE .cluster-label span p{background-color:transparent;}#mermaid-svg-w0MKYFQYylZ0zynE .label text,#mermaid-svg-w0MKYFQYylZ0zynE span{fill:#333;color:#333;}#mermaid-svg-w0MKYFQYylZ0zynE .node rect,#mermaid-svg-w0MKYFQYylZ0zynE .node circle,#mermaid-svg-w0MKYFQYylZ0zynE .node ellipse,#mermaid-svg-w0MKYFQYylZ0zynE .node polygon,#mermaid-svg-w0MKYFQYylZ0zynE .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-w0MKYFQYylZ0zynE .rough-node .label text,#mermaid-svg-w0MKYFQYylZ0zynE .node .label text,#mermaid-svg-w0MKYFQYylZ0zynE .image-shape .label,#mermaid-svg-w0MKYFQYylZ0zynE .icon-shape .label{text-anchor:middle;}#mermaid-svg-w0MKYFQYylZ0zynE .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-w0MKYFQYylZ0zynE .rough-node .label,#mermaid-svg-w0MKYFQYylZ0zynE .node .label,#mermaid-svg-w0MKYFQYylZ0zynE .image-shape .label,#mermaid-svg-w0MKYFQYylZ0zynE .icon-shape .label{text-align:center;}#mermaid-svg-w0MKYFQYylZ0zynE .node.clickable{cursor:pointer;}#mermaid-svg-w0MKYFQYylZ0zynE .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-w0MKYFQYylZ0zynE .arrowheadPath{fill:#333333;}#mermaid-svg-w0MKYFQYylZ0zynE .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-w0MKYFQYylZ0zynE .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-w0MKYFQYylZ0zynE .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w0MKYFQYylZ0zynE .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-w0MKYFQYylZ0zynE .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w0MKYFQYylZ0zynE .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-w0MKYFQYylZ0zynE .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-w0MKYFQYylZ0zynE .cluster text{fill:#333;}#mermaid-svg-w0MKYFQYylZ0zynE .cluster span{color:#333;}#mermaid-svg-w0MKYFQYylZ0zynE div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-w0MKYFQYylZ0zynE .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-w0MKYFQYylZ0zynE rect.text{fill:none;stroke-width:0;}#mermaid-svg-w0MKYFQYylZ0zynE .icon-shape,#mermaid-svg-w0MKYFQYylZ0zynE .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-w0MKYFQYylZ0zynE .icon-shape p,#mermaid-svg-w0MKYFQYylZ0zynE .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-w0MKYFQYylZ0zynE .icon-shape .label rect,#mermaid-svg-w0MKYFQYylZ0zynE .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-w0MKYFQYylZ0zynE .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-w0MKYFQYylZ0zynE .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-w0MKYFQYylZ0zynE :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端SQL
SP:解析、优化
确定分区与数据位置
插入ESEND / ERECV
拆成多个子计划
QC调度
BP/SP上的SQC和工作线程
结果汇总并返回

1. 计划生成:先按单机方式优化,再考虑分布式执行

DMDPC并不是完全重新设计一套SQL优化器。它先基于DM8原有能力生成单机逻辑计划,再在连接、分组、排序、去重等需要跨节点协作的位置,评估是否插入数据交换操作符ESENDERECV

优化器会结合:

  • 表和分区所在的BP;
  • 分区键和连接键是否一致;
  • 统计信息和数据量;
  • 可用节点及并行度;
  • 网络传输代价;
  • 广播、重分发、汇总等不同路径的代价;

选择总代价较低的方案。官方计划生成机制

所以,同一条连接SQL,可能出现四种典型执行形态:

场景 主要动作 代价特征
两表分区完全对齐 各BP本地连接 最理想,网络交换少
小表连接大表 小表广播到多个执行节点 小表足够小时效果好
两边大表分布不一致 两边按连接键重分发 网络与CPU开销较大
最终结果需要全局排序或汇总 数据汇集或归并 并行度可能降低

这也是前一部分强调"经常关联的表尽量使用相同分区组"的原因:表设计会直接改变执行计划。

2. ESEND和ERECV:分布式执行计划的分界线

ESEND负责发送数据,ERECV负责接收数据。

例如BP1扫描订单数据后,若结果需要交给SP或其他BP继续连接、聚合,就会通过:

text 复制代码
BP1上的ESEND  →  网络传输  →  目标节点上的ERECV

DMDPC支持六种基本分发方式:

分发方式 含义 常见用途
DIRECT 直接发送给目标线程 局部结果上送、单目标处理
BROADCAST 每批数据发送给全部接收者 小表广播参与连接
BY HASH 按键的哈希值分发 按连接键重分布、分区智能连接
BY RANGE 按值范围发送 范围处理、排序类场景
BY LIST 按枚举值发送 列表分区或枚举类分布
BY N_DEST 按目标并行线程数哈希分发 不依赖物理分区的并行分发

其中最常见、最值得关注的是BROADCASTBY HASHBY N_DEST官方数据交换操作符说明

看到执行计划中有大量ESEND/ERECV,不代表计划一定有问题;它说明存在跨节点协作。真正要关注的是:

  • 发送的数据量是否过大;
  • 是否发生了不必要的双边重分发;
  • 广播的是否真是小表;
  • 数据分发后是否均衡;
  • 汇总是否被集中到单一节点成为瓶颈。

3. GI:决定哪些分区、哪些数据页由哪些线程扫描

GI(Granule Iterator)是DMDPC中控制数据扫描粒度的操作符。

它主要做两件事:

  • 给不同工作线程分配要扫描的分区或数据页;
  • 根据分区条件裁剪不需要访问的分区。

例如按月份范围分区:

sql 复制代码
SELECT *
FROM T_ORDER
WHERE ORDER_TIME >= '2026-07-01'
  AND ORDER_TIME < '2026-08-01';

优化器若能在生成计划时确定条件,只扫描7月对应分区,称为静态分区裁剪

如果条件是参数、变量,或者分区列出现在等值连接条件中,执行时再决定访问哪些分区,称为动态分区裁剪 。计划附加信息中的dynamic_pll可帮助判断是否使用了动态裁剪。官方GI与分区裁剪说明

从运维角度看,GI出现时可以重点判断:

  • 是否只扫描了必要分区;
  • 线程是否均衡;
  • 一个热点分区是否被单独压在某个BP上;
  • 分区智能连接是否真正生效。

4. 子计划与子任务:一份计划的"设计态"和"运行态"

通信算子ESEND/ERECV会把整体计划划分为多个可调度单元,切分后的每一块称为子计划。二者在执行计划树中不一定文本相邻,但代表同一条生产者---消费者数据通道。

  • 计划生成阶段叫SPLAN(子计划);
  • 真正执行阶段叫STASK(子任务);
  • 两者本质上是同一个对象在不同阶段的名称。

ESEND位于生产者侧发送输出,ERECV在消费者侧接收并向上层传递结果;不要仅凭"根/叶"口诀判断方向。子任务的根操作符及其sites(...)信息才用于判断执行站点和并行度。每个子计划可以拥有自己的执行节点、并行度和工作线程。

例如一条跨节点查询可能被切为:

text 复制代码
子计划0:BP1扫描订单分区并发送结果
子计划1:BP2扫描明细分区并发送结果
子计划2:SP执行连接和局部聚合
子计划3:SP或BP执行最终汇总并返回

子计划之间存在父子依赖关系,但不是完全串行。下层子任务一旦开始输出第一批数据,上层子任务就可以被调度,形成流水线并行处理。官方子计划分割与调度机制

5. QC与SQC:谁负责调度

  • QC(Query Coordinator)位于发起SQL的SP上,负责整个查询的调度控制。
  • SQC(Sub Query Coordinator)位于执行子任务的BP或SP上,负责协调本节点的子任务线程,并向QC反馈完成情况。

可以理解为:

text 复制代码
QC:总调度
SQC:每个执行节点上的现场调度
工作线程:真正扫描、连接、聚合和发送数据

不同子任务可以有不同并行度;同一个子任务在不同BP上也可能分配不同数量的线程。这样能够适应不同数据量和节点负载,而不是强制所有节点使用相同线程数。

6. 计算与存储分离在执行时怎样体现

DMDPC倾向于把接近数据的工作放在BP:

  • 扫描;
  • 过滤;
  • 投影;
  • 部分聚合;
  • 本地连接。

将中间层计算或需要多节点协作的工作放在SP:

  • 跨分区连接;
  • 双边重分发后的连接;
  • 全局去重、分组、排序;
  • 最终结果汇总。

当复杂SQL确实需要多个SP参与时,系统会优先选择当前连接SP所在SP组的节点;未定义SP组时,会优先考虑与涉及BP同机的SP;若数量仍不足,再从全局SP中选择,最多16个。官方计算与存储分离规则

所以,SP组不是单纯为了连接高可用,它会影响复杂SQL可使用的计算资源。

7. 怎样看执行计划

日常第一步仍然是:

sql 复制代码
EXPLAIN <SQL语句>;

在DMDPC计划中,重点观察:

  • GI:扫描了哪些分区、是否有裁剪;
  • ESEND/ERECV:是否存在跨节点数据传输;
  • type(BROADCAST):是否广播了小表;
  • type(N_DEST)或哈希分发:是否发生重分发;
  • stask_no:子任务编号;
  • sites(...):涉及的执行站点和线程;
  • pwj_opt:是否与分区智能连接相关;
  • 并行度与实际涉及节点数是否合理。

如果需要更细地看物理计划拆分过程,可开启:

sql 复制代码
CALL SP_SET_DBG_SHOW('PHD', 1);

官方示例显示,PHD阶段会独立展示各子任务,包括子任务编号、数据来源、依赖关系、分区粒度、执行节点与工作线程信息。官方查询分析手册

8. 计划正常,但执行慢时看什么

启用ENABLE_MONITOR=1后,可查询:

sql 复制代码
SELECT *
FROM V$DPC_STASK_THRD
WHERE EXEC_ID = <执行号>;

重点字段包括:

  • TIME_USED:线程耗时;
  • FIRST_ROW_USED:返回第一行的耗时;
  • N_ROWS_SENDN_BYTES_SEND:发送数据量;
  • N_ROWS_RECV:接收数据量;
  • P_WAIT_TIMES:开启限流时的资源等待次数;
  • IS_OVER:线程是否完成。

若同一子任务中,一个线程处理百万行、另一个只处理几百行,通常意味着数据倾斜或分发不均;若P_WAIT_TIMES明显增大,可能存在上游发送快、下游消费慢导致的链路堆积。官方子任务监控视图说明

这一部分最该记住的是:

  1. ESEND/ERECV是跨节点协作的边界。
  2. 子计划是拆分后的执行单元,QC统筹、SQC落地。
  3. GI决定扫描粒度,并承担分区裁剪。
  4. 性能优先级通常是:减少无效扫描 → 减少网络搬运 → 避免数据倾斜 → 再考虑提高并行度。

第4部分:分布式事务与数据可见性

这一部分要先分清三件事:

机制 解决的问题
单机事务机制 一个BP内的修改如何提交、回滚、隔离
两阶段提交(2PC) 一笔事务跨多个BP时,怎样保证整体原子性
RAFT 同一BP或MP数据的主备副本怎样保持一致、怎样故障切换

RAFT不是分布式事务;2PC也不是主备复制。它们共同保证DMDPC的一致性,但作用范围完全不同。


1. 什么情况下会产生分布式事务

如果一笔事务访问的数据全部位于同一个BP RAFT组,它本质上仍以该BP为主要执行与提交位置。

当一笔事务同时修改多个BP RAFT组的数据时,它就是分布式事务。例如:

sql 复制代码
BEGIN;

UPDATE T_ORDER
SET STATUS = 'PAID'
WHERE ORDER_ID = 10001;   -- 位于 BP_RAFT_1

INSERT INTO T_PAY_LOG (ORDER_ID, PAY_STATUS, PAY_TIME)
VALUES (10001, 'PAID', CURRENT_TIMESTAMP);  -- 假设该行位于 BP_RAFT_2

COMMIT;

这时不能出现:

text 复制代码
订单状态已改为已支付
但支付流水没有写入

DMDPC通过两阶段提交,让参与的BP最终保持"全提交或全回滚"。SP作为全局事务协调者,BP作为参与者。官方分布式事务机制
BP组2 BP组1 SP协调者 客户端 BP组2 BP组1 SP协调者 客户端 #mermaid-svg-2G39iVmzLOu8ohxR{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-2G39iVmzLOu8ohxR .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-2G39iVmzLOu8ohxR .error-icon{fill:#552222;}#mermaid-svg-2G39iVmzLOu8ohxR .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-2G39iVmzLOu8ohxR .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-2G39iVmzLOu8ohxR .marker{fill:#333333;stroke:#333333;}#mermaid-svg-2G39iVmzLOu8ohxR .marker.cross{stroke:#333333;}#mermaid-svg-2G39iVmzLOu8ohxR svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-2G39iVmzLOu8ohxR p{margin:0;}#mermaid-svg-2G39iVmzLOu8ohxR .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2G39iVmzLOu8ohxR text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-2G39iVmzLOu8ohxR .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2G39iVmzLOu8ohxR .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-2G39iVmzLOu8ohxR .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-2G39iVmzLOu8ohxR .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-2G39iVmzLOu8ohxR #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-2G39iVmzLOu8ohxR .sequenceNumber{fill:white;}#mermaid-svg-2G39iVmzLOu8ohxR #sequencenumber{fill:#333;}#mermaid-svg-2G39iVmzLOu8ohxR #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-2G39iVmzLOu8ohxR .messageText{fill:#333;stroke:none;}#mermaid-svg-2G39iVmzLOu8ohxR .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2G39iVmzLOu8ohxR .labelText,#mermaid-svg-2G39iVmzLOu8ohxR .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-2G39iVmzLOu8ohxR .loopText,#mermaid-svg-2G39iVmzLOu8ohxR .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-2G39iVmzLOu8ohxR .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-2G39iVmzLOu8ohxR .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-2G39iVmzLOu8ohxR .noteText,#mermaid-svg-2G39iVmzLOu8ohxR .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-2G39iVmzLOu8ohxR .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2G39iVmzLOu8ohxR .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2G39iVmzLOu8ohxR .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-2G39iVmzLOu8ohxR .actorPopupMenu{position:absolute;}#mermaid-svg-2G39iVmzLOu8ohxR .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-2G39iVmzLOu8ohxR .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-2G39iVmzLOu8ohxR .actor-man circle,#mermaid-svg-2G39iVmzLOu8ohxR line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-2G39iVmzLOu8ohxR :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} COMMIT 预提交 预提交 成功 成功 COMMIT COMMIT 成功 成功 提交成功

2. 第一阶段:预提交

客户端执行COMMIT后,SP不会立刻告诉客户端"提交成功",而是先向所有参与事务的BP发送预提交请求。

每个BP会:

  1. 检查事务是否可以提交;
  2. 将相关操作生成回滚日志并写入回滚段;
  3. 返回预提交成功或失败。

只有所有参与BP都确认预提交成功,SP才会进入第二阶段。

若其中一个BP明确失败,或SP无法判断某个BP是否已经完成预提交,SP会尝试通知仍存活的BP回滚:

  • 能确认回滚时,向客户端返回事务提交失败;
  • 无法确认最终状态时,可能返回"未知的提交结果"。

官方规定,无论客户端收到"事务提交失败"还是"未知的提交结果",都应视为该次事务执行失败。实际业务中应通过全局唯一业务单号、幂等写入和后续查询确认最终业务状态,而不是无条件重复提交。官方预提交流程

3. 第二阶段:最终提交

当所有BP预提交成功后:

  1. SP向全部参与BP广播COMMIT
  2. BP完成提交、释放事务资源;
  3. BP将成功结果返回SP;
  4. SP确认后向客户端返回提交成功。

如果第二阶段发生SP与某个BP的通信中断,官方机制是:SP将未完成的提交交给异步任务处理,并立即向客户端返回提交成功;恢复通信后,异步任务会继续执行提交,直到成功。

这体现了两阶段提交的关键逻辑:

text 复制代码
预提交阶段:必须先确认全部参与者可以完成
提交阶段:一旦全局决定提交,最终会持续推进所有参与者完成提交

4. DPC_2PC参数:生产环境不要轻易关闭

DPC_2PC控制是否采用两阶段提交:

含义
0 关闭2PC。无故障时提交效率可能更高,但故障时无法保证跨BP事务一致性
1 启用2PC,当前官方默认值
23 启用2PC,并启用事务ID缓存与快照序号缓存优化
45 启用2PC,并启用事务ID缓存与快照序号本地优化
67 启用2PC,同时启用事务ID缓存、快照序号缓存与快照序号本地优化

对生产系统而言,除非明确知道业务不会出现跨BP事务,且能够接受故障情况下的一致性风险,否则不应把它设为027属于优化组合,应先在目标版本和真实负载下验证,不应仅为追求提交性能而直接修改。

另外,DPC_2PC属于由MP统一管理的参数。SP、BP本地配置与MP注册值不一致时,启动会按MP中的实际注册值覆盖本地设置。因此排查参数时,要以集群生效配置为准,而不是只看某台机器上的dm.ini官方参数说明

5. 为什么有了2PC,还需要全局时钟

2PC解决"是否整体提交",但不能独立解决并发事务的数据可见性。

假设事务T1跨BP1、BP2更新数据。BP1先完成提交动作,BP2稍后才完成;此时另一个事务T2如果正好读取两边数据,就可能看到"一边新、一边旧"的状态。

因此,DMDPC通过由MP统一管理的全局时钟系统GTS,为BP提供一致的时间序列判断依据。

BP在以下场景都会向MP申请当前全局时钟值:

  • 事务内执行语句;
  • 预提交;
  • 最终提交。

事务读取数据时,会取得当前时钟值cur_seq。只有在这个时钟点之前已经完成提交的数据,才对当前事务可见。官方数据可见性机制

6. CA和MCA:判断数据是否可见的两层记录

这里不需要死记内部数组结构,理解职责即可。

位置 结构 保存什么
BP CA 本BP涉及事务的TID、预提交/提交状态、对应时钟值
MP MCA 已执行提交操作事务的TID和提交时钟值

一笔事务的记录过程是:

text 复制代码
修改数据
  ↓
日志中记录该修改对应的全局事务ID(TID)
  ↓
预提交:BP的CA登记 TID + 预提交状态 + 时钟值
  ↓
最终提交:MP的MCA登记 TID + 提交时钟值
  ↓
BP的CA更新为 TID + 提交状态 + 提交时钟值

读取某一行数据时,BP会:

  1. 从日志记录中找到修改这行数据的事务ID;
  2. 在CA中查询该事务状态和时钟值;
  3. 将其与当前事务的cur_seq比较;
  4. 如果CA仍是预提交状态,再去MP的MCA确认该事务是否最终提交。

只有确认该事务在当前cur_seq之前已提交,数据才可见;其他情况均不可见。

这套机制解决了"多个BP实际提交时刻并不完全相同"的问题,让不同节点对同一事务结果形成一致的可见性判断。

7. 对设计和运维意味着什么

第一,尽量降低无意义的跨BP事务。

跨BP事务需要SP协调、预提交、全局时钟和多节点通信,天然比本地事务成本高。对高频关联、同时更新的表,尽量通过一致分区键和相同分区组,让相关数据共定位。

第二,不要把未知提交结果当成普通失败简单重试。

应用层应具有幂等机制,例如订单号、流水号唯一约束;出现未知结果时先按业务主键查询状态,再决定是否补偿或重试。

第三,长事务在DMDPC中更值得警惕。

长事务可能长期占用多个BP资源,也会扩大分布式协调和可见性维护成本。可从DPC_LONG_TRX查看长事务所在RAFT组、事务ID和登记时间。

sql 复制代码
SELECT *
FROM DPC_LONG_TRX;

第四,排查一致性问题时要分层。

  • 某笔跨分片事务提交异常:先看2PC参与BP和SP通信;
  • 同一数据分片主备状态异常:看RAFT、归档和副本日志;
  • 查询结果像是"新旧不一致":排查事务状态、长事务和快照时钟,而不是只看某个BP的数据文件。

这一部分最该记住的四句话

  1. 2PC保证跨BP事务要么全提交、要么全回滚。
  2. SP是全局事务协调者,BP是参与者。
  3. GTS解决跨BP提交存在时间差时的数据可见性问题。
  4. RAFT保证同一份数据的副本一致,2PC保证一笔事务跨多份数据的一致。

第5部分:RAFT多副本、高可用与故障切换

DMDPC的高可用不是"整个集群只有一套主备",而是以RAFT组为单位实现。

  • MP只有一套元数据,因此最多只有一个MP RAFT组;
  • 一个DMDPC集群可以配置多个BP RAFT组,每个RAFT组保存不同的数据分片;
  • 每个BP或MP RAFT组内部可配置单副本或多副本;
  • SP不保存持久数据,不采用这种多副本数据复制模式。

所以,某个BP RAFT组切换主备,不会导致整个DMDPC所有BP一起切换。


1. RAFT组中的角色

一个三副本BP RAFT组可以表示为:

text 复制代码
BP1      → Leader,对外提供主库服务
BP1_R1   → Follower,备库
BP1_R2   → Follower,备库

正常情况下:

  • Leader作为主库;
  • Follower作为备库;
  • 每个副本保存同一份数据;
  • Leader持续向Follower发送心跳和Redo日志;
  • 日志获得多数节点确认后,才被视为已提交。

当需要动态新增副本时,新节点会先作为LEARNER加入。LEARNER负责同步数据,但不参与选举和日志提交推进;同步完成并转为正式成员后,才能成为Follower、Candidate或Leader。

DMDPC当前官方指标中,单个多副本系统最大支持9个副本;多副本成员数应为奇数。官方RAFT组定义与指标

2. 多数派:高可用的真正边界

RAFT不是"有一个备库就一定高可用",关键在于多数派。

副本数 形成多数派需要 最多可容忍故障节点数
1 1 0
3 2 1
5 3 2
7 4 3

例如三副本组:

text 复制代码
BP1 + BP1_R1 + BP1_R2
  • 存活3个:正常;
  • 存活2个:仍有多数派,可选主、可提交;
  • 只存活1个:没有多数派,不能安全选主,也不能继续推进新的提交。

官方规定,活动节点数必须超过配置总节点数的一半,才能发起选举;如果多数节点故障,系统暂时无法自动选举新主库。官方故障处理机制

因此,三副本不等于"允许掉两台机器",而是"允许掉一台机器"。

3. Leader怎样被选出来

正常Leader会定期广播心跳。备库在选举超时时间内未收到心跳,会认为Leader可能故障,并发起选举。

基本过程:
Follower 3 Follower 2 Follower 1 Follower 3 Follower 2 Follower 1 #mermaid-svg-5TruWVpm1QBANHMq{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-5TruWVpm1QBANHMq .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-5TruWVpm1QBANHMq .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-5TruWVpm1QBANHMq .error-icon{fill:#552222;}#mermaid-svg-5TruWVpm1QBANHMq .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-5TruWVpm1QBANHMq .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-5TruWVpm1QBANHMq .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-5TruWVpm1QBANHMq .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-5TruWVpm1QBANHMq .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-5TruWVpm1QBANHMq .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-5TruWVpm1QBANHMq .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-5TruWVpm1QBANHMq .marker{fill:#333333;stroke:#333333;}#mermaid-svg-5TruWVpm1QBANHMq .marker.cross{stroke:#333333;}#mermaid-svg-5TruWVpm1QBANHMq svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-5TruWVpm1QBANHMq p{margin:0;}#mermaid-svg-5TruWVpm1QBANHMq .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5TruWVpm1QBANHMq text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-5TruWVpm1QBANHMq .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5TruWVpm1QBANHMq .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-5TruWVpm1QBANHMq .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-5TruWVpm1QBANHMq .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-5TruWVpm1QBANHMq #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-5TruWVpm1QBANHMq .sequenceNumber{fill:white;}#mermaid-svg-5TruWVpm1QBANHMq #sequencenumber{fill:#333;}#mermaid-svg-5TruWVpm1QBANHMq #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-5TruWVpm1QBANHMq .messageText{fill:#333;stroke:none;}#mermaid-svg-5TruWVpm1QBANHMq .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5TruWVpm1QBANHMq .labelText,#mermaid-svg-5TruWVpm1QBANHMq .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-5TruWVpm1QBANHMq .loopText,#mermaid-svg-5TruWVpm1QBANHMq .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-5TruWVpm1QBANHMq .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-5TruWVpm1QBANHMq .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-5TruWVpm1QBANHMq .noteText,#mermaid-svg-5TruWVpm1QBANHMq .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-5TruWVpm1QBANHMq .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5TruWVpm1QBANHMq .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5TruWVpm1QBANHMq .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-5TruWVpm1QBANHMq .actorPopupMenu{position:absolute;}#mermaid-svg-5TruWVpm1QBANHMq .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-5TruWVpm1QBANHMq .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-5TruWVpm1QBANHMq .actor-man circle,#mermaid-svg-5TruWVpm1QBANHMq line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-5TruWVpm1QBANHMq :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} Leader心跳超时 转为Candidate,任期号+1 请求投票 请求投票 投票 获得多数票,成为Leader

选举时:

  • 发起者转为CANDIDATE
  • 当前Leader任期号L_TERM_ID加1;
  • 每个节点在同一任期内只有一票;
  • 节点只会投给符合日志有效性要求的候选者;
  • 获得超过半数选票的节点成为Leader;
  • 新Leader自动切换为主库模式和OPEN状态,其余节点转为备库。

不是所有节点都会主动发起选举,只有开启选举开关的节点才会做超时检测并主动竞选。官方自动选主规则

4. 日志怎样复制与提交

DMDPC通过RAFT归档复制Redo日志。

Leader产生Redo日志包RLOG_PKG后:

  1. 产生日志包后,通过XMAL并行、异步发送给备库;
  2. 备库校验日志来源、任期和连续性;
  3. 备库将日志包排序、缓存、重演并写入本地日志;
  4. 备库向Leader反馈自身已刷盘的日志包序号与LSN;
  5. Leader收到超过半数节点已刷盘的确认后,推进提交位点。

这里有两个重要指标:

  • C_SEQNO:已经提交的日志包序号;
  • C_LSN:已提交日志包中的最大LSN。

只有日志在超过半数节点完成刷盘后,Leader才能推进C_SEQNO/C_LSN;备库再根据Leader通过日志和心跳携带的信息推进自己的提交位点。

因此,已提交数据具备多数副本保障;但日志发送本身是异步并行的,不是Leader每产生一个日志包就逐个等待所有备库返回。官方RAFT归档与日志提交机制

5. 为什么未提交日志不能直接刷成数据页

RAFT允许在选举后截断旧Leader上未被多数派确认的日志。

因此,DMDPC对数据页刷盘和检查点有额外限制:

  • 数据页对应的LSN必须小于或等于C_LSN,才能写入磁盘;
  • 检查点最多只能推进到C_LSN
  • 不能将仅存在于本地、尚未被多数派确认的修改持久化为数据页。

这就是为什么Leader故障后,旧Leader重新加入时,可能需要截断本地已写入但未提交、且新Leader不存在的日志。这样才能防止旧主库的"孤立写入"污染当前一致性状态。官方检查点与恢复机制

6. 三类故障分别会怎样

故障场景 系统行为 是否继续服务
Leader故障,但仍有多数节点存活 备库选举新Leader 可以
少数Follower故障 主库将故障副本归档状态置为无效,不再向其同步 通常可以
多数节点故障 无法形成多数派,不能安全选主或推进新提交 不可以
原Leader恢复 若已有新Leader,则降为Follower并异步追赶 恢复后重新加入
Follower恢复 先异步同步到与Leader一致,再恢复正常复制和提交参与 恢复后重新加入

若少数备库故障,主库仍可在多数派内推进日志提交;若多数备库故障,主库新日志无法得到多数节点确认,事务提交可能挂起。

故障备库恢复时,Leader会自动发起异步恢复。若旧主库在故障前留下未提交日志,或备库落后过多,恢复流程也可能先进行日志截断,再追赶当前主库。官方自动故障处理与恢复

7. 与2PC的边界再确认一次

假设一笔订单事务同时修改BP_RAFT_ABP_RAFT_B

text 复制代码
2PC:
SP协调 A 和 B,保证订单事务整体提交或整体回滚。

RAFT:
A内部的主、备副本保持A的数据一致;
B内部的主、备副本保持B的数据一致。

#mermaid-svg-dkiXOWMmsDXmtEci{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dkiXOWMmsDXmtEci .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dkiXOWMmsDXmtEci .error-icon{fill:#552222;}#mermaid-svg-dkiXOWMmsDXmtEci .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dkiXOWMmsDXmtEci .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dkiXOWMmsDXmtEci .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dkiXOWMmsDXmtEci .marker.cross{stroke:#333333;}#mermaid-svg-dkiXOWMmsDXmtEci svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dkiXOWMmsDXmtEci p{margin:0;}#mermaid-svg-dkiXOWMmsDXmtEci .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dkiXOWMmsDXmtEci .cluster-label text{fill:#333;}#mermaid-svg-dkiXOWMmsDXmtEci .cluster-label span{color:#333;}#mermaid-svg-dkiXOWMmsDXmtEci .cluster-label span p{background-color:transparent;}#mermaid-svg-dkiXOWMmsDXmtEci .label text,#mermaid-svg-dkiXOWMmsDXmtEci span{fill:#333;color:#333;}#mermaid-svg-dkiXOWMmsDXmtEci .node rect,#mermaid-svg-dkiXOWMmsDXmtEci .node circle,#mermaid-svg-dkiXOWMmsDXmtEci .node ellipse,#mermaid-svg-dkiXOWMmsDXmtEci .node polygon,#mermaid-svg-dkiXOWMmsDXmtEci .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dkiXOWMmsDXmtEci .rough-node .label text,#mermaid-svg-dkiXOWMmsDXmtEci .node .label text,#mermaid-svg-dkiXOWMmsDXmtEci .image-shape .label,#mermaid-svg-dkiXOWMmsDXmtEci .icon-shape .label{text-anchor:middle;}#mermaid-svg-dkiXOWMmsDXmtEci .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dkiXOWMmsDXmtEci .rough-node .label,#mermaid-svg-dkiXOWMmsDXmtEci .node .label,#mermaid-svg-dkiXOWMmsDXmtEci .image-shape .label,#mermaid-svg-dkiXOWMmsDXmtEci .icon-shape .label{text-align:center;}#mermaid-svg-dkiXOWMmsDXmtEci .node.clickable{cursor:pointer;}#mermaid-svg-dkiXOWMmsDXmtEci .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dkiXOWMmsDXmtEci .arrowheadPath{fill:#333333;}#mermaid-svg-dkiXOWMmsDXmtEci .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dkiXOWMmsDXmtEci .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dkiXOWMmsDXmtEci .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dkiXOWMmsDXmtEci .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dkiXOWMmsDXmtEci .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dkiXOWMmsDXmtEci .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dkiXOWMmsDXmtEci .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dkiXOWMmsDXmtEci .cluster text{fill:#333;}#mermaid-svg-dkiXOWMmsDXmtEci .cluster span{color:#333;}#mermaid-svg-dkiXOWMmsDXmtEci div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dkiXOWMmsDXmtEci .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dkiXOWMmsDXmtEci rect.text{fill:none;stroke-width:0;}#mermaid-svg-dkiXOWMmsDXmtEci .icon-shape,#mermaid-svg-dkiXOWMmsDXmtEci .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dkiXOWMmsDXmtEci .icon-shape p,#mermaid-svg-dkiXOWMmsDXmtEci .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dkiXOWMmsDXmtEci .icon-shape .label rect,#mermaid-svg-dkiXOWMmsDXmtEci .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dkiXOWMmsDXmtEci .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dkiXOWMmsDXmtEci .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dkiXOWMmsDXmtEci :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SP:2PC协调事务
BP_RAFT_A
BP_RAFT_B
A主 + A备副本
B主 + B备副本

一句话概括:

2PC解决"多个数据分片之间的一致提交",RAFT解决"同一个数据分片多个副本之间的一致复制和故障切换"。

8. 多副本配置中最关键的几个参数

DMARCH.INI承担本地归档与RAFT归档配置。

需要重点理解:

参数 作用
RAFT_HB_INTERVAL Leader广播心跳的间隔
RAFT_VOTE_INTERVAL 选举超时;必须至少为心跳间隔的两倍
RAFT_SELF_ID 当前副本在RAFT组中的节点编号
ARCH_TYPE=RAFT 声明该归档目标为RAFT副本
RAFT_LEARNER=1ARCH_TYPE=LEARNER 仅在动态增删节点流程中标识、同步学习者节点
本地归档 多副本使用RAFT归档时,至少还必须配置一路本地归档

官方建议各副本设置不同的RAFT_VOTE_INTERVAL,数值更小的实例被选为新主库的优先级更高。该参数不宜在不了解当前网络抖动和业务容忍切换时间的情况下随意调低,否则可能增加误判和无效选举风险。官方多副本参数说明

LEARNER不是静态三副本部署的常规配置。动态新增副本时,应按官方节点变更流程配合SP_ADD_RAFT_LEARNER等过程设置学习者角色和LEARNER归档;数据同步完成并转为正式成员后,才参与选举与日志提交推进。

9. 日常巡检最应该看什么

多副本环境下,最重要的视图之一是:

sql 复制代码
SELECT *
FROM V$RLOG_RAFT_INFO;

重点字段:

字段 巡检意义
STATE 当前角色:Leader、Follower等
LEADER 当前主库实例名
L_TERM_ID / TERM_ID 前者为当前Leader任期,后者为已刷盘日志任期;应结合二者判断选举与刷盘状态
C_SEQC_LSN 当前提交位点
F_SEQ_ARRF_LSN_ARR 各副本已刷盘进度,仅主库有效
NEED_WAIT 备库是否达到日志堆积上限
HP_FLAG 当前节点是否存在日志堆积
SWITCH_TIME 最近切换时间
ARCH_CHG_STAT 是否正在进行成员变更

该视图仅在多副本系统中具有真实参考意义;单副本环境下其值没有实际诊断意义。官方动态视图说明

10. 计划内切换不能直接"手工改主备"

DMDPC支持手动切换主库,但有严格前提:

  • 必须是多副本环境;
  • 目标节点必须是有效、OPEN状态的Follower备库;
  • 当前存在有效Leader;
  • 目标备库归档必须有效;
  • 不允许在成员增删尚未完成时切换。

标准过程是:

  1. 直连当前MP或BP主库;BS主库需要使用LOCAL方式登录;
  2. 执行SP_RAFT_SUSPEND_THREAD(),挂起主库其他工作线程;
  3. 直连目标备库,执行SP_RAFT_SWITCHOVER()

这类操作属于计划内主备切换,适用于维护和演练;不要用直接修改节点模式的方式代替正常RAFT切换流程。官方手动切换流程

11. 影子库:节约存储的特殊成员

DMDPC还支持影子库(Shadow库):

  • 不修改数据文件,不保存完整可服务数据;
  • 可以参与选举和事务提交;
  • 仅提供动态视图查询;
  • 数量不能超过整个多副本集群节点数的一半;
  • 不会通过Redo重演完成数据库版本升级,升级后需要重构影子库。

它是对副本拓扑的补充,不应把它理解成普通可随时接管业务数据服务的完整BP/MP副本。

这一部分最该记住的是:

  1. 高可用边界是每一个RAFT组,而不是整个DMDPC一键主备切换。
  2. 超过半数副本刷盘,日志才能成为已提交日志。
  3. 多数派决定能否选主,也决定能否继续提交新事务。
  4. 故障节点恢复后会以Follower身份异步追赶;未提交旧日志必要时会被截断。
  5. 计划内切换走官方手动切换流程,故障切换依靠RAFT自动选举。

第6部分:部署规划与最小单副本集群

先明确这一阶段的目标:不是直接搭高可用生产集群,而是先搭一套能真实观察分布式存储、跨BP查询和执行计划的单副本DMDPC实验集群

官方定义的最小集群是:

text 复制代码
1个MP + 1个SP + 1个BP

但只有一个BP时,无法直观看到数据跨节点分布和子任务调度。因此官方单副本示例实际使用:

text 复制代码
1个MP + 1个SP + 2个BP

后续再从这套环境扩展到BP三副本、MP三副本和多SP。官方集群部署说明


1. 测试环境与生产环境的部署思路不同

场景 推荐部署方式 目的
学习、测试 可将MP、SP、BP部署在同一台主机的不同目录和端口 降低实验门槛
功能验证 至少部署2个BP 验证分区分布、跨BP执行和计划拆分
生产单副本 建议MP、SP、BP分别部署在不同主机 降低角色互相争抢资源的影响
生产高可用 MP与每个BP RAFT组跨故障域部署多副本 保障节点或机房故障下的服务连续性

官方文档给出的最低硬件要求是每台主机至少2GB内存;测试环境可混布,生产环境建议每个主机只部署一种类型的服务器角色。官方软硬件环境要求

这其中要注意:2GB只是最低环境条件,并不是生产容量规划建议。实际生产还需要根据BP数据量、并行查询、连接数、归档、备份和副本数量单独评估CPU、内存、磁盘与网络。


2. 一个单副本实验拓扑长什么样

建议你后续实验直接采用下面这个结构:
#mermaid-svg-KuyfVf39FvKF3jxO{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-KuyfVf39FvKF3jxO .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-KuyfVf39FvKF3jxO .error-icon{fill:#552222;}#mermaid-svg-KuyfVf39FvKF3jxO .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-KuyfVf39FvKF3jxO .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-KuyfVf39FvKF3jxO .marker{fill:#333333;stroke:#333333;}#mermaid-svg-KuyfVf39FvKF3jxO .marker.cross{stroke:#333333;}#mermaid-svg-KuyfVf39FvKF3jxO svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-KuyfVf39FvKF3jxO p{margin:0;}#mermaid-svg-KuyfVf39FvKF3jxO .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-KuyfVf39FvKF3jxO .cluster-label text{fill:#333;}#mermaid-svg-KuyfVf39FvKF3jxO .cluster-label span{color:#333;}#mermaid-svg-KuyfVf39FvKF3jxO .cluster-label span p{background-color:transparent;}#mermaid-svg-KuyfVf39FvKF3jxO .label text,#mermaid-svg-KuyfVf39FvKF3jxO span{fill:#333;color:#333;}#mermaid-svg-KuyfVf39FvKF3jxO .node rect,#mermaid-svg-KuyfVf39FvKF3jxO .node circle,#mermaid-svg-KuyfVf39FvKF3jxO .node ellipse,#mermaid-svg-KuyfVf39FvKF3jxO .node polygon,#mermaid-svg-KuyfVf39FvKF3jxO .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-KuyfVf39FvKF3jxO .rough-node .label text,#mermaid-svg-KuyfVf39FvKF3jxO .node .label text,#mermaid-svg-KuyfVf39FvKF3jxO .image-shape .label,#mermaid-svg-KuyfVf39FvKF3jxO .icon-shape .label{text-anchor:middle;}#mermaid-svg-KuyfVf39FvKF3jxO .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-KuyfVf39FvKF3jxO .rough-node .label,#mermaid-svg-KuyfVf39FvKF3jxO .node .label,#mermaid-svg-KuyfVf39FvKF3jxO .image-shape .label,#mermaid-svg-KuyfVf39FvKF3jxO .icon-shape .label{text-align:center;}#mermaid-svg-KuyfVf39FvKF3jxO .node.clickable{cursor:pointer;}#mermaid-svg-KuyfVf39FvKF3jxO .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-KuyfVf39FvKF3jxO .arrowheadPath{fill:#333333;}#mermaid-svg-KuyfVf39FvKF3jxO .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-KuyfVf39FvKF3jxO .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-KuyfVf39FvKF3jxO .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KuyfVf39FvKF3jxO .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-KuyfVf39FvKF3jxO .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KuyfVf39FvKF3jxO .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-KuyfVf39FvKF3jxO .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-KuyfVf39FvKF3jxO .cluster text{fill:#333;}#mermaid-svg-KuyfVf39FvKF3jxO .cluster span{color:#333;}#mermaid-svg-KuyfVf39FvKF3jxO div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-KuyfVf39FvKF3jxO .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-KuyfVf39FvKF3jxO rect.text{fill:none;stroke-width:0;}#mermaid-svg-KuyfVf39FvKF3jxO .icon-shape,#mermaid-svg-KuyfVf39FvKF3jxO .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-KuyfVf39FvKF3jxO .icon-shape p,#mermaid-svg-KuyfVf39FvKF3jxO .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-KuyfVf39FvKF3jxO .icon-shape .label rect,#mermaid-svg-KuyfVf39FvKF3jxO .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-KuyfVf39FvKF3jxO .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-KuyfVf39FvKF3jxO .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-KuyfVf39FvKF3jxO :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端 / DIsql
SP1

SQL接入与调度
MP1

元数据
BP1 / RAFT_1

数据分片1
BP2 / RAFT_2

数据分片2

对应关系:

角色 实例数 RAFT组 作用
MP 1 MP_RAFT 管理元数据和集群注册信息
SP 1 RAFT_SP1 接收SQL、生成和调度计划
BP 1 RAFT_1 保存第一批表空间和分区
BP 1 RAFT_2 保存第二批表空间和分区
BP组 1 BG_1={RAFT_1, RAFT_2} 建表或创建表空间时的可选存储资源池

此时每个BP RAFT组内只有一个BP,因此不具备BP主备容灾;它的价值是帮助你先理解分布式架构和后续多副本扩展路径。


3. 部署前必须完成的规划

部署前建议先整理一张资源规划表,而不是边部署边决定名称、端口和目录。

项目 规划原则
实例名称 SP、BP、MP全局唯一,建议体现角色,如SP1BP1MP1
数据目录 每个实例独立目录,不能混用
PORT_NUM 数据库监听端口,每个实例唯一
AP_PORT_NUM DMDPC节点内部协同通信端口,每个实例唯一
MP.INI中的MP_PORT 不能与MP、BP、SP的AP_PORT_NUM冲突
BP RAFT名称 每一个数据分片使用独立RAFT组,如RAFT_1RAFT_2
BP组名称 用于收纳多个BP RAFT组,如BG_1
SP RAFT名称 每个SP需要注册到SP类型RAFT组,如RAFT_SP1
IP地址 多机部署时应预先确定内外网地址及网络可达性

官方命令行示例中,同一台主机上为SP、两个BP和MP分别分配独立的实例端口、AP端口和数据路径;Linux与Windows的部署逻辑一致。官方单副本部署示例


4. 初始化阶段:角色在此时就固定

DMDPC实例使用dminit初始化。初始化时要指定:

text 复制代码
PATH
INSTANCE_NAME
PORT_NUM
AP_PORT_NUM
DPC_MODE

其中DPC_MODE决定实例角色:

角色 初始化值
MP MP1
BP BP2
SP SP3

概念性示例如下:

bash 复制代码
dminit path=/dmdata/sp1 instance_name=SP1 \
port_num=5236 ap_port_num=6000 dpc_mode=SP

dminit path=/dmdata/bp1 instance_name=BP1 \
port_num=5237 ap_port_num=6001 dpc_mode=BP

dminit path=/dmdata/mp1 instance_name=MP1 \
port_num=5239 ap_port_num=6003 dpc_mode=MP

最关键的限制是:

实例角色在初始化时确定,之后不能修改。

例如,已经按BP初始化的实例,不能通过改配置文件把它变成MP或SP重新使用;角色规划错误通常需要重新初始化对应实例。官方DPC_MODE说明


5. MP.INI:所有节点认识MP的入口

MP是元数据中心。SP、BP需要通过MP.INI找到MP,因此在MP、SP和BP的数据库目录下都需要配置MP.INI

单MP时,其核心内容是:

ini 复制代码
mp_host = <MP地址>
mp_port = <MP通信端口>

同一集群内,SP和BP上的MP.INI内容必须一致。

这不是普通业务连接配置,而是集群内部找到元数据服务的配置。若MP地址、端口或配置不一致,SP/BP无法正确加入或运行在同一个DMDPC集群中。官方MP.INI说明


6. 最重要的部署顺序

单副本集群正确部署流程如下:
#mermaid-svg-dd4blhW5oEaiqSON{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dd4blhW5oEaiqSON .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dd4blhW5oEaiqSON .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dd4blhW5oEaiqSON .error-icon{fill:#552222;}#mermaid-svg-dd4blhW5oEaiqSON .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dd4blhW5oEaiqSON .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dd4blhW5oEaiqSON .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dd4blhW5oEaiqSON .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dd4blhW5oEaiqSON .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dd4blhW5oEaiqSON .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dd4blhW5oEaiqSON .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dd4blhW5oEaiqSON .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dd4blhW5oEaiqSON .marker.cross{stroke:#333333;}#mermaid-svg-dd4blhW5oEaiqSON svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dd4blhW5oEaiqSON p{margin:0;}#mermaid-svg-dd4blhW5oEaiqSON .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dd4blhW5oEaiqSON .cluster-label text{fill:#333;}#mermaid-svg-dd4blhW5oEaiqSON .cluster-label span{color:#333;}#mermaid-svg-dd4blhW5oEaiqSON .cluster-label span p{background-color:transparent;}#mermaid-svg-dd4blhW5oEaiqSON .label text,#mermaid-svg-dd4blhW5oEaiqSON span{fill:#333;color:#333;}#mermaid-svg-dd4blhW5oEaiqSON .node rect,#mermaid-svg-dd4blhW5oEaiqSON .node circle,#mermaid-svg-dd4blhW5oEaiqSON .node ellipse,#mermaid-svg-dd4blhW5oEaiqSON .node polygon,#mermaid-svg-dd4blhW5oEaiqSON .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dd4blhW5oEaiqSON .rough-node .label text,#mermaid-svg-dd4blhW5oEaiqSON .node .label text,#mermaid-svg-dd4blhW5oEaiqSON .image-shape .label,#mermaid-svg-dd4blhW5oEaiqSON .icon-shape .label{text-anchor:middle;}#mermaid-svg-dd4blhW5oEaiqSON .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dd4blhW5oEaiqSON .rough-node .label,#mermaid-svg-dd4blhW5oEaiqSON .node .label,#mermaid-svg-dd4blhW5oEaiqSON .image-shape .label,#mermaid-svg-dd4blhW5oEaiqSON .icon-shape .label{text-align:center;}#mermaid-svg-dd4blhW5oEaiqSON .node.clickable{cursor:pointer;}#mermaid-svg-dd4blhW5oEaiqSON .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dd4blhW5oEaiqSON .arrowheadPath{fill:#333333;}#mermaid-svg-dd4blhW5oEaiqSON .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dd4blhW5oEaiqSON .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dd4blhW5oEaiqSON .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dd4blhW5oEaiqSON .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dd4blhW5oEaiqSON .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dd4blhW5oEaiqSON .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dd4blhW5oEaiqSON .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dd4blhW5oEaiqSON .cluster text{fill:#333;}#mermaid-svg-dd4blhW5oEaiqSON .cluster span{color:#333;}#mermaid-svg-dd4blhW5oEaiqSON div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dd4blhW5oEaiqSON .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dd4blhW5oEaiqSON rect.text{fill:none;stroke-width:0;}#mermaid-svg-dd4blhW5oEaiqSON .icon-shape,#mermaid-svg-dd4blhW5oEaiqSON .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dd4blhW5oEaiqSON .icon-shape p,#mermaid-svg-dd4blhW5oEaiqSON .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dd4blhW5oEaiqSON .icon-shape .label rect,#mermaid-svg-dd4blhW5oEaiqSON .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dd4blhW5oEaiqSON .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dd4blhW5oEaiqSON .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dd4blhW5oEaiqSON :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 初始化MP、SP、BP实例
所有实例配置MP.INI
启动MP
登录MP,先注册当前MP
注册BP RAFT组、BP实例、BP组
注册SP RAFT组和SP实例
启动SP与BP
连接SP验证集群

逐步理解:

第一步:初始化所有实例

MP、SP、BP都先通过dminit完成独立初始化。

第二步:配置MP.INI

为MP、SP、BP写入MP地址和端口。

第三步:先启动MP
bash 复制代码
dmserver /dmdata/mp1/DAMENG/dm.ini dpc_mode=MP

MP需要最先启动,并在DMDPC运行期间持续保持开启。

第四步:登录MP,先注册当前MP自身

只有先把当前登录的MP注册到集群,才能继续注册其他SP、BP节点。

MP使用固定的MP_RAFT概念;注册时RAFT名称可使用NULLMP_RAFT

第五步:注册BP和BP组

每增加一个单副本BP,通常需要:

  1. 创建BP RAFT组;
  2. 在该RAFT组中注册BP实例;
  3. 创建或复用BP组;
  4. 将BP RAFT组加入BP组。

例如逻辑上:

text 复制代码
创建 RAFT_1 → 注册 BP1 → 加入 BG_1
创建 RAFT_2 → 注册 BP2 → 加入 BG_1
第六步:注册SP

SP也需要先创建SP类型RAFT组,再注册SP实例。

第七步:启动SP和BP

只有MP启动、节点注册完成后,SP与BP才能第一次启动。

官方特别强调:

SP和BP加入DMDPC集群的注册步骤,必须在SP、BP第一次启动前完成。

注册完成后,SP和BP的启动没有先后顺序。官方注册与启动流程


7. 为什么"先注册、后首次启动"这么关键

注册信息保存在MP管理的集群元数据中,包括:

  • 实例名称;
  • 角色;
  • RAFT组;
  • IP地址;
  • 实例端口与AP端口;
  • 主备模式;
  • 节点状态;
  • BP组和SP组归属。

SP、BP首次启动时需要按MP中的注册信息确定自己在集群中的身份与通信关系。

因此,正确理解不是"先把四个DM实例都启动起来,再让它们互相发现",而是:

先让MP知道整个集群拓扑,再启动SP和BP作为已登记成员加入。


8. 部署完成后的验证标准

一个正常的单副本DMDPC应满足:

  • MP、SP、BP均已经启动,且全部实例状态为OPEN
  • BP不是未启动、宕机或非OPEN状态;
  • 可以连接SP;
  • 从SP能查询到集群中的实例与RAFT组信息;
  • BP组、BP RAFT组、实例注册信息完整。

验证时可连接SP:

sql 复制代码
SELECT * FROM V$INSTANCE;
SELECT * FROM DPC_BP_GROUP;
SELECT * FROM DPC_BP_RAFT;
SELECT * FROM DPC_INSTANCE;

业务使用中,应用只应连接SP获取完整分布式数据库服务。MP和BP通常仅在监控、备份、故障处理、切换等维护场景下直连;官方明确不建议在MP或BP上执行大量业务SQL。官方连接与验证要求


9. 正确停止顺序

正常关闭顺序为:

text 复制代码
SP → BP → MP

也就是先停止对外计算与接入,再停存储节点,最后停止元数据节点。

官方提示,若不按顺序退出,可能导致剩余节点异常退出;下次启动时需要进行更多Redo日志处理,恢复时间会变长。官方退出顺序


这一部分最该记住的五句话

  1. 单副本学习环境建议至少1个SP、2个BP、1个MP。
  2. 实例的SP/BP/MP角色在dminit初始化时确定,不能后改。
  3. MP先启动,先注册MP自身,再注册BP和SP。
  4. SP、BP必须先完成注册,才能第一次启动。
  5. 业务连接SP;正常停止顺序是SP→BP→MP。

第7部分:BP 多副本 RAFT 组部署

这一部分只解决一个目标:把"一个 BP 实例存一份数据"升级为"一个 BP RAFT 组内有 3 个 BP 副本,自动选主并保持数据一致"。

关键边界先说清:

  • BP 组是多个 BP RAFT 组的资源集合,用于承载多个数据分片。
  • BP RAFT 组才是一份数据的多副本单元。
  • 因此,"两份数据分片、每份三副本"的拓扑是:1 个 BP 组 → 2 个 BP RAFT 组 → 每组 3 个 BP 实例

官方示例为 1 SP、1 MP、2 个 BP RAFT 组;每个 BP RAFT 组包含 3 个副本。MP 在该示例中仍是单副本。 官方部署文档
#mermaid-svg-mAx2uKZ9fj34ndhB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-mAx2uKZ9fj34ndhB .error-icon{fill:#552222;}#mermaid-svg-mAx2uKZ9fj34ndhB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-mAx2uKZ9fj34ndhB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-mAx2uKZ9fj34ndhB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-mAx2uKZ9fj34ndhB .marker.cross{stroke:#333333;}#mermaid-svg-mAx2uKZ9fj34ndhB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-mAx2uKZ9fj34ndhB p{margin:0;}#mermaid-svg-mAx2uKZ9fj34ndhB .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster-label text{fill:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster-label span{color:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster-label span p{background-color:transparent;}#mermaid-svg-mAx2uKZ9fj34ndhB .label text,#mermaid-svg-mAx2uKZ9fj34ndhB span{fill:#333;color:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB .node rect,#mermaid-svg-mAx2uKZ9fj34ndhB .node circle,#mermaid-svg-mAx2uKZ9fj34ndhB .node ellipse,#mermaid-svg-mAx2uKZ9fj34ndhB .node polygon,#mermaid-svg-mAx2uKZ9fj34ndhB .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-mAx2uKZ9fj34ndhB .rough-node .label text,#mermaid-svg-mAx2uKZ9fj34ndhB .node .label text,#mermaid-svg-mAx2uKZ9fj34ndhB .image-shape .label,#mermaid-svg-mAx2uKZ9fj34ndhB .icon-shape .label{text-anchor:middle;}#mermaid-svg-mAx2uKZ9fj34ndhB .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-mAx2uKZ9fj34ndhB .rough-node .label,#mermaid-svg-mAx2uKZ9fj34ndhB .node .label,#mermaid-svg-mAx2uKZ9fj34ndhB .image-shape .label,#mermaid-svg-mAx2uKZ9fj34ndhB .icon-shape .label{text-align:center;}#mermaid-svg-mAx2uKZ9fj34ndhB .node.clickable{cursor:pointer;}#mermaid-svg-mAx2uKZ9fj34ndhB .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-mAx2uKZ9fj34ndhB .arrowheadPath{fill:#333333;}#mermaid-svg-mAx2uKZ9fj34ndhB .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-mAx2uKZ9fj34ndhB .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-mAx2uKZ9fj34ndhB .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mAx2uKZ9fj34ndhB .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-mAx2uKZ9fj34ndhB .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mAx2uKZ9fj34ndhB .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster text{fill:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB .cluster span{color:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-mAx2uKZ9fj34ndhB .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-mAx2uKZ9fj34ndhB rect.text{fill:none;stroke-width:0;}#mermaid-svg-mAx2uKZ9fj34ndhB .icon-shape,#mermaid-svg-mAx2uKZ9fj34ndhB .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-mAx2uKZ9fj34ndhB .icon-shape p,#mermaid-svg-mAx2uKZ9fj34ndhB .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-mAx2uKZ9fj34ndhB .icon-shape .label rect,#mermaid-svg-mAx2uKZ9fj34ndhB .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-mAx2uKZ9fj34ndhB .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-mAx2uKZ9fj34ndhB .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-mAx2uKZ9fj34ndhB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SP1:业务连接与调度
MP:元数据
BP 组 BG_1
RAFT_1:一个数据副本集
RAFT_2:另一个数据副本集
BP11
BP12
BP13
BP21
BP22
BP23

1. 规划与初始化

以两组、三副本为例,需要初始化 8 个实例:

角色 实例
MP MP
SP SP1
RAFT_1 的 BP 副本 BP11、BP12、BP13
RAFT_2 的 BP 副本 BP21、BP22、BP23

初始化时分别指定不可变的 dpc_mode=MP/SP/BP,并保证实例名、实例端口 PORT_NUM、内部通信端口 AP_PORT_NUM 全局不冲突。之后为全部 SP、BP、MP 实例放置一致的 MP.INI,其中 mp_port 不能与各实例的 AP_PORT_NUM 冲突。 官方部署示例

2. 先注册拓扑,再启动 SP/BP

启动 MP 后,直连 MP 注册集群对象。顺序上的硬要求是:

  1. 先注册当前登录的 MP。
  2. 创建 RAFT_1、注册 BP11/BP12/BP13。
  3. 创建 RAFT_2、注册 BP21/BP22/BP23。
  4. 创建 BG_1,把 RAFT_1、RAFT_2 加进去。
  5. 创建并注册 SP1。

BP 副本在注册时以 STANDBY、无效状态登记;后续成功选出主库后,MP 中的模式与状态会自动更新。

下面给出两组、三副本拓扑的完整注册模板 。其中 IP 地址和端口必须替换为与各实例 dm.iniAP_PORT_NUMPORT_NUM 一致的实际值;不能保留尖括号内容直接执行。

sql 复制代码
-- BP RAFT_1:BP11、BP12、BP13
SP_CREATE_DPC_RAFT('BP', 'RAFT_1');
SP_CREATE_DPC_INSTANCE('RAFT_1', 'BP11', 'BP', 6002, 5238,
                       '<BP11内网IP>', '<BP11外网IP>', 'STANDBY', 0, 'BP replica 1');
SP_CREATE_DPC_INSTANCE('RAFT_1', 'BP12', 'BP', 6003, 5239,
                       '<BP12内网IP>', '<BP12外网IP>', 'STANDBY', 0, 'BP replica 2');
SP_CREATE_DPC_INSTANCE('RAFT_1', 'BP13', 'BP', 6004, 5240,
                       '<BP13内网IP>', '<BP13外网IP>', 'STANDBY', 0, 'BP replica 3');

-- BP RAFT_2:BP21、BP22、BP23
SP_CREATE_DPC_RAFT('BP', 'RAFT_2');
SP_CREATE_DPC_INSTANCE('RAFT_2', 'BP21', 'BP', 6005, 5241,
                       '<BP21内网IP>', '<BP21外网IP>', 'STANDBY', 0, 'BP replica 1');
SP_CREATE_DPC_INSTANCE('RAFT_2', 'BP22', 'BP', 6006, 5242,
                       '<BP22内网IP>', '<BP22外网IP>', 'STANDBY', 0, 'BP replica 2');
SP_CREATE_DPC_INSTANCE('RAFT_2', 'BP23', 'BP', 6007, 5243,
                       '<BP23内网IP>', '<BP23外网IP>', 'STANDBY', 0, 'BP replica 3');

-- 将两组 BP RAFT 组纳入同一个 BP 组
SP_CREATE_DPC_BP_GROUP('BG_1', 'bp group1');
SP_BP_GROUP_ADD_RAFT('BG_1', 'RAFT_1');
SP_BP_GROUP_ADD_RAFT('BG_1', 'RAFT_2');

-- SP 也必须先注册到独立的 SP 类型 RAFT 组
SP_CREATE_DPC_RAFT('SP', 'RAFT_SP1');
SP_CREATE_DPC_INSTANCE('RAFT_SP1', 'SP1', 'SP', 6001, 5236,
                       '<SP1内网IP>', '<SP1外网IP>', 'NORMAL', 1, 'SP instance');

这里最容易踩坑:SP 和 BP 必须在各自第一次启动前完成注册。 官方部署步骤

注册后先查:

sql 复制代码
SELECT * FROM DPC_BP_GROUP;
SELECT * FROM DPC_BP_RAFT;
SELECT * FROM DPC_INSTANCE;

此时 BP 显示 STANDBY、未生效是正常的;它们还没有组成真正运行的 RAFT 组。

3. 为每个 RAFT 组准备同源数据

这是多副本部署最关键的一步。

RAFT_1 为例:

  1. 先只把 BP11 配为本地归档并启动到 OPEN
  2. 正常退出 BP11,做 BP11 的脱机全备
  3. 将该备份还原到 BP12、BP13;
  4. 对 BP12、BP13 执行 RECOVER ... UPDATE DB_MAGIC
  5. 再为三者配置完整的 RAFT 归档。

这样做的目的,是让 RAFT_1 的 3 个成员从同一份数据库基线出发。RAFT_2 也必须独立重复这个过程,不能拿 RAFT_1 的备份集去还原 RAFT_2官方数据准备步骤

概念上可以记成:

同一 RAFT 组内副本:同源备份初始化。

不同 RAFT 组:各自独立的数据副本集,不能混用备份。

4. 配置 dm.iniDMARCH.INI

每一个 BP 副本的 dm.ini 至少应有:

ini 复制代码
ARCH_INI = 1
ALTER_MODE_STATUS = 0

ARCH_INI=1 启用归档配置;ALTER_MODE_STATUS=0 禁止用户直接用 SQL 修改服务器模式。 官方配置说明

对 RAFT_1 的每个 BP,DMARCH.INI 都要配置:

  • 自己唯一的 RAFT_SELF_ID
  • 指向组内另外两个成员 的两条 ARCH_TYPE = RAFT
  • 一条 ARCH_TYPE = LOCAL 本地归档。

以下是BP11的结构示意,非完整生产配置ARCH_DEST路径必须替换为真实目录,并根据归档保留策略补充或显式确认ARCH_FILE_SIZEARCH_SPACE_LIMIT、目录权限和可用磁盘空间:

ini 复制代码
XMAL_HB_INTERVAL = 5
RAFT_HB_INTERVAL = 150
RAFT_VOTE_INTERVAL = 1500
RAFT_SELF_ID = 1

[ARCHIVE_RAFT1]
ARCH_TYPE = RAFT
ARCH_DEST = BP12
ARCH_DEST_ID = 2

[ARCHIVE_RAFT2]
ARCH_TYPE = RAFT
ARCH_DEST = BP13
ARCH_DEST_ID = 3

[ARCHIVE_LOCAL1]
ARCH_TYPE = LOCAL
ARCH_DEST = <BP11本地归档目录>
ARCH_FILE_SIZE = 128
ARCH_SPACE_LIMIT = 0

BP12、BP13 同理:各自 RAFT_SELF_ID 分别为 2、3,且都要把另外两个副本写成 RAFT 归档目标。原因是故障后任一副本都可能成为 Leader,不能只按"现在谁是主库"单向配置。官方示例也明确要求同时配置 RAFT 归档和本地归档。 官方 RAFT 归档配置

5. 启动与自动选主

SP 与 BP 启动没有强制先后;多副本 BP 使用 MOUNT 启动,例如:

bash 复制代码
dmserver <sp_dm.ini> dpc_mode=SP
dmserver <bp11_dm.ini> dpc_mode=BP MOUNT
dmserver <bp12_dm.ini> dpc_mode=BP MOUNT
dmserver <bp13_dm.ini> dpc_mode=BP MOUNT

同一组的 3 个 BP 启动顺序不作要求。启动后,组内会自动完成选举:

  • 1 个副本成为 Primary / OPEN
  • 其余成为 Standby / OPEN
  • 每个 BP RAFT 组都选出主库后,DMDPC 才具备可用 BP。

若有任一 BP 组没有主库,或主库不是 Primary / OPEN,集群会被判定为无可用 BP。正常业务只连接 SP;直连 MP/BP 限于监控和维护。 官方启动与验收要求

6. 上线验收清单

在 SP 上完成:

sql 复制代码
SELECT * FROM V$INSTANCE;
SELECT * FROM DPC_INSTANCE;

再到每个 BP RAFT 组的主库或维护连接上检查 RAFT 状态,例如 V$RLOG_RAFT_INFO。重点不是死记字段,而是确认:

  • 每个 RAFT 组恰有一个主库;
  • 主库为 Primary / OPEN
  • 其他成员在线、处于备库状态;
  • SP 可见所有实例;
  • 业务只连接 SP 后可正常访问数据。

DPC_INSTANCE 中包含各实例的角色、通信端口、系统模式、系统状态和有效状态;V$RLOG_RAFT_INFO 用于观察多副本 RAFT 运行状态。 官方系统表与动态视图说明

本节你应该真正建立的认识是:BP 多副本不是"多开几个 BP",而是先让同一 RAFT 组成员拥有同源数据,再通过双向 RAFT 归档组成可选主、可复制、可恢复的一致性单元。

第8部分:MP 多副本------控制面高可用

BP 多副本保护的是用户数据分片 ;MP 多副本保护的是元数据与全局协调能力 。MP 提供元数据服务,并统一管理全局时钟 GTS;BP 执行事务时会向 MP 申请全局时钟值。因此,MP 的可用性直接影响整个 DMDPC 的协调面。 基本概念 关键技术

DMDPC 中 MP 最多只有一个 MP RAFT 组。3 个 MP 副本属于同一份元数据的副本集,而不是像 BP 那样存在多个数据分片 RAFT 组。
#mermaid-svg-dMl3jH8KicW2pSYS{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-dMl3jH8KicW2pSYS .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-dMl3jH8KicW2pSYS .error-icon{fill:#552222;}#mermaid-svg-dMl3jH8KicW2pSYS .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-dMl3jH8KicW2pSYS .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-dMl3jH8KicW2pSYS .marker{fill:#333333;stroke:#333333;}#mermaid-svg-dMl3jH8KicW2pSYS .marker.cross{stroke:#333333;}#mermaid-svg-dMl3jH8KicW2pSYS svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-dMl3jH8KicW2pSYS p{margin:0;}#mermaid-svg-dMl3jH8KicW2pSYS .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-dMl3jH8KicW2pSYS .cluster-label text{fill:#333;}#mermaid-svg-dMl3jH8KicW2pSYS .cluster-label span{color:#333;}#mermaid-svg-dMl3jH8KicW2pSYS .cluster-label span p{background-color:transparent;}#mermaid-svg-dMl3jH8KicW2pSYS .label text,#mermaid-svg-dMl3jH8KicW2pSYS span{fill:#333;color:#333;}#mermaid-svg-dMl3jH8KicW2pSYS .node rect,#mermaid-svg-dMl3jH8KicW2pSYS .node circle,#mermaid-svg-dMl3jH8KicW2pSYS .node ellipse,#mermaid-svg-dMl3jH8KicW2pSYS .node polygon,#mermaid-svg-dMl3jH8KicW2pSYS .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-dMl3jH8KicW2pSYS .rough-node .label text,#mermaid-svg-dMl3jH8KicW2pSYS .node .label text,#mermaid-svg-dMl3jH8KicW2pSYS .image-shape .label,#mermaid-svg-dMl3jH8KicW2pSYS .icon-shape .label{text-anchor:middle;}#mermaid-svg-dMl3jH8KicW2pSYS .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-dMl3jH8KicW2pSYS .rough-node .label,#mermaid-svg-dMl3jH8KicW2pSYS .node .label,#mermaid-svg-dMl3jH8KicW2pSYS .image-shape .label,#mermaid-svg-dMl3jH8KicW2pSYS .icon-shape .label{text-align:center;}#mermaid-svg-dMl3jH8KicW2pSYS .node.clickable{cursor:pointer;}#mermaid-svg-dMl3jH8KicW2pSYS .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-dMl3jH8KicW2pSYS .arrowheadPath{fill:#333333;}#mermaid-svg-dMl3jH8KicW2pSYS .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-dMl3jH8KicW2pSYS .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-dMl3jH8KicW2pSYS .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dMl3jH8KicW2pSYS .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-dMl3jH8KicW2pSYS .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dMl3jH8KicW2pSYS .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-dMl3jH8KicW2pSYS .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-dMl3jH8KicW2pSYS .cluster text{fill:#333;}#mermaid-svg-dMl3jH8KicW2pSYS .cluster span{color:#333;}#mermaid-svg-dMl3jH8KicW2pSYS div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-dMl3jH8KicW2pSYS .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-dMl3jH8KicW2pSYS rect.text{fill:none;stroke-width:0;}#mermaid-svg-dMl3jH8KicW2pSYS .icon-shape,#mermaid-svg-dMl3jH8KicW2pSYS .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-dMl3jH8KicW2pSYS .icon-shape p,#mermaid-svg-dMl3jH8KicW2pSYS .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-dMl3jH8KicW2pSYS .icon-shape .label rect,#mermaid-svg-dMl3jH8KicW2pSYS .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-dMl3jH8KicW2pSYS .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-dMl3jH8KicW2pSYS .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-dMl3jH8KicW2pSYS :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SP:接收业务、生成计划
BP:存储和执行子任务
MP_RAFT:元数据与全局时钟
MP1
MP2
MP3

1. 官方示例拓扑

官方的 MP 多副本示例是:

角色 数量 副本方式
SP 1 单副本
BP 1 单副本
MP 3 同一 MP_RAFT 的三副本

实际生产当然可以同时采用"BP 多副本 + MP 多副本";这里故意把 BP 设为单副本,是为了单独说明 MP 高可用的部署流程。 官方 MP 多副本部署示例

2. MP.INI 是与单 MP 最大的配置差异

单 MP 时,MP.INI 只有一个 mp_host/mp_port

MP 多副本时,所有 SP、BP、MP 实例 都必须保存包含全部 MP 成员的同一份 MP.INI

ini 复制代码
[MP1]
mp_host = <MP1地址>
mp_port = <MP1配置端口>

[MP2]
mp_host = <MP2地址>
mp_port = <MP2配置端口>

[MP3]
mp_host = <MP3地址>
mp_port = <MP3配置端口>

其中每个 mp_port 均不能与 MP、SP、BP 的 AP_PORT_NUM 冲突。不要把它理解成"业务连接端口池";它是各节点定位 MP 集群成员的配置。 官方 MP.INI 配置

3. 部署顺序:先以 MP1 建立元数据基线

流程可记为:

  1. 初始化 SP1、BP1、MP1/MP2/MP3;
  2. 向全部实例写入完整 MP.INI
  3. MP1 先配置本地归档并正常启动;
  4. 登录 MP1,注册 MP1、MP2、MP3,再注册 BP 与 SP;
  5. 退出 MP1,做脱机全备;
  6. 将 MP1 的备份还原到 MP2、MP3,并执行 UPDATE DB_MAGIC
  7. 配置三个 MP 的 RAFT 归档;
  8. 三个 MP 都以 MOUNT 启动,等待自动选主;
  9. MP 选主成功后,再启动 SP 和 BP。

MP 注册时也属于 MP_RAFT,该 RAFT 组名可显式写 MP_RAFT,也可以传 NULL。注册阶段的 MP1、MP2、MP3 均以 STANDBY 和无效状态登记,选主后由系统自动更新状态。 官方注册与数据准备步骤

4. 为什么 MP2、MP3 必须由 MP1 的备份还原

三台 MP 要组成一个 RAFT 组,首先必须拥有同源的元数据基线。官方要求:

  • MP1 停库后进行脱机备份;
  • MP2、MP3 由此备份集还原;
  • 对 MP2、MP3 执行 RECOVER ... UPDATE DB_MAGIC
  • 若确需在这个阶段启动 MP2/MP3,只能以 MOUNT 方式启动,避免写入新日志而造成三个节点日志不一致。

这与上一节 BP RAFT 组的"同源数据初始化"是同一类原则,只是 MP 同步的是全局元数据与协调状态。 官方 MP RAFT 数据准备

5. MP 的 RAFT 归档配置

三个 MP 的 dm.ini 都应配置:

ini 复制代码
ARCH_INI = 1
ALTER_MODE_STATUS = 0

每台 MP 的 DMARCH.INI 都包括:

  • 自己唯一的 RAFT_SELF_ID
  • 指向另两台 MP 的两条 ARCH_TYPE = RAFT
  • 本地归档 ARCH_TYPE = LOCAL
  • XMAL_HB_INTERVALRAFT_HB_INTERVALRAFT_VOTE_INTERVAL

以下是MP1的结构示意,非完整生产配置ARCH_DEST路径必须替换为真实目录,并根据归档保留策略补充或显式确认ARCH_FILE_SIZEARCH_SPACE_LIMIT、目录权限和可用磁盘空间:

ini 复制代码
RAFT_SELF_ID = 1

[ARCHIVE_RAFT1]
ARCH_TYPE = RAFT
ARCH_DEST = MP2
ARCH_DEST_ID = 2

[ARCHIVE_RAFT2]
ARCH_TYPE = RAFT
ARCH_DEST = MP3
ARCH_DEST_ID = 3

[ARCHIVE_LOCAL1]
ARCH_TYPE = LOCAL
ARCH_DEST = <MP1本地归档目录>
ARCH_FILE_SIZE = 128
ARCH_SPACE_LIMIT = 0

MP2、MP3 同理,且每台都要能向另外两台同步。RAFT_VOTE_INTERVAL 通常设置成不同值,降低同时发起选举的概率。 官方 MP RAFT 归档示例

6. 启动、验收与停机

三台 MP 均以 MOUNT 启动:

bash 复制代码
dmserver <mp1_dm.ini> dpc_mode=MP MOUNT
dmserver <mp2_dm.ini> dpc_mode=MP MOUNT
dmserver <mp3_dm.ini> dpc_mode=MP MOUNT

自动选出主 MP 后,再启动 SP 与 BP。一个正常的 DMDPC 系统要求 MP、SP、BP 都正常启动且处于 OPEN 状态;业务仍然只连接 SP。 官方启动与验收说明

正常停机时顺序是:

  1. 退出全部 SP;
  2. 退出全部 BP;
  3. 在 MP RAFT 组任一有效节点执行 exit all,协同退出该 RAFT 组;
  4. 再退出未参与协同退出的无效 MP。

本节结论

  • BP 多副本解决"数据分片副本"的高可用。
  • MP 多副本解决"元数据、GTS、控制面"的高可用。
  • 二者底层都依赖 RAFT,但 RAFT 组承载的对象不同。
  • 多 MP 最关键的配置不是多开三个实例,而是:全量一致的 MP.INI、同源元数据基线、双向 RAFT 归档、成功选主后再启动业务面。

第9部分:SP 高可用与业务接入

SP 的"高可用"不是 RAFT 主备切换,而是多 SP 横向部署 + 客户端重新接入 + MP 对故障 SP 事务的代理处理

原因很简单:SP 对外响应请求、生成和调度执行计划,但本身不存用户数据;它被登记到 RAFT 组只是为了统一管理,SP 不支持多副本 。因此不要把多个 SP 理解成同一份 SP 的主备副本。 基本概念 关键技术
#mermaid-svg-Bei6KpFn2OYbFIMm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-Bei6KpFn2OYbFIMm .error-icon{fill:#552222;}#mermaid-svg-Bei6KpFn2OYbFIMm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-Bei6KpFn2OYbFIMm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-Bei6KpFn2OYbFIMm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-Bei6KpFn2OYbFIMm .marker.cross{stroke:#333333;}#mermaid-svg-Bei6KpFn2OYbFIMm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-Bei6KpFn2OYbFIMm p{margin:0;}#mermaid-svg-Bei6KpFn2OYbFIMm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster-label text{fill:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster-label span{color:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster-label span p{background-color:transparent;}#mermaid-svg-Bei6KpFn2OYbFIMm .label text,#mermaid-svg-Bei6KpFn2OYbFIMm span{fill:#333;color:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm .node rect,#mermaid-svg-Bei6KpFn2OYbFIMm .node circle,#mermaid-svg-Bei6KpFn2OYbFIMm .node ellipse,#mermaid-svg-Bei6KpFn2OYbFIMm .node polygon,#mermaid-svg-Bei6KpFn2OYbFIMm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-Bei6KpFn2OYbFIMm .rough-node .label text,#mermaid-svg-Bei6KpFn2OYbFIMm .node .label text,#mermaid-svg-Bei6KpFn2OYbFIMm .image-shape .label,#mermaid-svg-Bei6KpFn2OYbFIMm .icon-shape .label{text-anchor:middle;}#mermaid-svg-Bei6KpFn2OYbFIMm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-Bei6KpFn2OYbFIMm .rough-node .label,#mermaid-svg-Bei6KpFn2OYbFIMm .node .label,#mermaid-svg-Bei6KpFn2OYbFIMm .image-shape .label,#mermaid-svg-Bei6KpFn2OYbFIMm .icon-shape .label{text-align:center;}#mermaid-svg-Bei6KpFn2OYbFIMm .node.clickable{cursor:pointer;}#mermaid-svg-Bei6KpFn2OYbFIMm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-Bei6KpFn2OYbFIMm .arrowheadPath{fill:#333333;}#mermaid-svg-Bei6KpFn2OYbFIMm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-Bei6KpFn2OYbFIMm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-Bei6KpFn2OYbFIMm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bei6KpFn2OYbFIMm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-Bei6KpFn2OYbFIMm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bei6KpFn2OYbFIMm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster text{fill:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm .cluster span{color:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-Bei6KpFn2OYbFIMm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-Bei6KpFn2OYbFIMm rect.text{fill:none;stroke-width:0;}#mermaid-svg-Bei6KpFn2OYbFIMm .icon-shape,#mermaid-svg-Bei6KpFn2OYbFIMm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-Bei6KpFn2OYbFIMm .icon-shape p,#mermaid-svg-Bei6KpFn2OYbFIMm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-Bei6KpFn2OYbFIMm .icon-shape .label rect,#mermaid-svg-Bei6KpFn2OYbFIMm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-Bei6KpFn2OYbFIMm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-Bei6KpFn2OYbFIMm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-Bei6KpFn2OYbFIMm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 应用 / 连接池 / 负载均衡
SP1
SP2
SP3
MP:元数据、故障SP事务代理
BP RAFT 组:数据与子任务

1. 多个 SP 能解决什么

增加 SP 主要有两类收益:

  • 承接更多客户端连接、分担 SQL 解析、优化、计划生成与调度压力;
  • 在某个 SP 不可用时,让新连接转向其他仍正常的 SP。

官方描述中,新加入的 SP 可以响应更多客户请求,并分担子计划执行;SP 可在单副本或多副本 DMDPC 中横向增加。 关键技术

但它不解决 BP 数据副本问题 ,也不替代 MP 的控制面高可用。完整高可用需要分别考虑:

主要手段
业务接入与计算 多 SP
元数据与 GTS MP 多副本
用户数据分片 BP RAFT 多副本

2. 增加一个 SP 的标准动作

SP 是横向扩展,新增 SP 时为它创建独立的 SP RAFT 组并注册实例:

sql 复制代码
SP_CREATE_DPC_RAFT('SP', 'RAFT_SP2');

SP_CREATE_DPC_INSTANCE(
  'RAFT_SP2', 'SP2', 'SP',
  <AP_PORT>, <PORT_NUM>,
  '<内网IP>', '<外网IP>',
  'NORMAL', 2, 'SP instance'
);

然后将当前集群一致的 MP.INI 放到 SP2 的 DAMENG 目录并启动 SP2。

一个 SP 类型的 RAFT 组最多只加入一个 SP,因此增加第二个 SP 时通常同时增加一个新的 SP RAFT 组。新增、删除 SP 可以在 SP 或 MP 上执行;删除前必须让该 SP 正常退出。 官方动态增加 SP 步骤

3. SP 组:不是高可用组,而是计算资源隔离组

多个 SP 可以按业务归入 SP 组。SP 组的价值在于:

  • 把不同应用或租户的计算节点隔离;
  • 影响计划生成阶段可使用的计算节点范围;
  • 便于为批处理、在线交易、报表等工作负载划分资源。

所以"SP 组"解决的是算力治理与隔离 ,不是 SP 的主备关系。 官方 SP 组定义

4. SP 故障时,事务与连接分别看

这两件事不能混为一谈。

**连接:**客户端连接在 SP1 上,SP1 故障后该连接不能继续使用;应用侧应通过连接池、多个 SP 地址或负载均衡,将后续连接建立到可用 SP。

这是由 SP 是客户端入口、且不具备多副本接管机制推导出的接入设计要求。

**已提交事务:**客户端已收到提交成功,表示事务已完成。

**正在提交的事务:**DMDPC 的两阶段提交由 SP 协调 BP;官方参数 DPC_PROXY_TIMEOUT 用于设置 MP 代理处理故障 SP 事务的最小故障时间。文档说明 SP 故障后至少约 5~10 秒才会由 MP 代理处理,默认阈值为 600 秒。 DMDPC 配置参数

这意味着:MP 的代理能力处理的是故障 SP 留下的事务协调问题,不能把它理解成客户端 SQL 会话无感迁移。因此,应用仍要具备连接重试和事务幂等/结果确认能力。

5. 运维上应监控什么

至少在 SP 上定期检查:

sql 复制代码
SELECT * FROM V$INSTANCE;
SELECT * FROM V$SESSIONS;

前者确认各 SP 是否可见、状态是否正常;后者可区分全局会话与当前 SP 本地会话。需要评估 SP 负载不均时,可使用:

sql 复制代码
CALL SP_DPC_REBALANCE_SESSION(1);

它会收集当前 SP 可见的所有 SP 会话数,生成平衡方案并开启引流;之后会话会在事务提交或回滚时切换到较空闲的SP。该能力要求会话通过服务名连接;引流尚未结束时不要重复执行SP_DPC_REBALANCE_SESSION(1)。如需清理方案并关闭引流,执行SP_DPC_REBALANCE_SESSION(0)集群信息查看 会话再平衡过程

本节结论:

SP 的核心是"无状态计算入口的横向扩展",而不是"一个 SP 故障、另一个 SP 原连接接管"。

第10部分:备份、恢复与容灾边界

核心结论:RAFT 多副本解决副本/节点故障下的持续服务;备份与归档解决误操作、逻辑损坏、整套环境重建和按时间点恢复。两者不能互相替代。

DMDPC 中 SP 不存储数据,所以不参与备份与还原;需要备份的是 MP 与 BP。多副本环境只备份每个 RAFT 组的主节点,但恢复时要覆盖该组全部成员。 官方备份与还原概述

目标 主要手段 关键范围
多副本中的少数成员故障 优先由 RAFT 自动恢复或按副本重建流程处理 一个 RAFT 组
明确只需恢复单个 MP/BP 的本地库 单库备份/还原 一个 MP 或 BP;不保证整集群事务一致性
整集群回退、异地重建或需要恢复业务一致性 整集群备份/还原 MP + 全部 BP
指定时间点恢复 完全备份 + 归档 MP + 全部 BP,且须满足 DPC_LOG_INTERVAL 前置条件

1. DMDPC 备份对象的正确理解

  • SP:没有用户数据,不备份。
  • MP:保存元数据,必须纳入备份。
  • BP:保存用户数据,所有数据所在的 BP RAFT 组都必须纳入备份。
  • 多副本 RAFT 组:备份主节点;从节点不支持备份。
  • 还原多副本 RAFT 组:组内全部节点使用同一份备份集进行还原。

"主节点备份"只是降低备份操作量,不表示其他副本不重要;还原时仍需把整个副本集恢复到一致的基线。 官方备份对象说明

2. 备份粒度:单库与整集群

单库备份用于明确范围内的单个 MP 或 BP 恢复。可选择联机或脱机备份,但还原只能脱机执行。它不是整集群一致性恢复的替代方案;若目标是恢复整套业务集群,应使用同批 MP/BP 备份集执行整集群恢复。

整集群备份才适用于整套 DMDPC 的一致性恢复:

  • 联机:连接任意一个 SP 执行一次备份,由集群协同备份 MP 与全部 BP。
  • 脱机:单副本集群逐一备份 MP 与所有 BP;多副本集群只备每个 RAFT 组主节点。
  • 整集群还原:所有 MP、BP 分别执行脱机还原。

换言之,正常生产备份的首选入口是 SP ,而不是分别直连 MP/BP 随意备份。 官方整集群备份与还原流程

3. BAK_MAGIC:判断是否同一批备份

DMDPC 的整集群恢复最容易出错的点,是把不同时间、不同批次的 MP/BP 备份混在一起。

  • DPC_MAGIC:标识一套 DMDPC 集群;同一集群的 MP 与 BP 具有相同值。
  • BAK_MAGIC:标识同一批 MP/BP 备份集。
  • 从 SP 发起的联机整集群备份,BAK_MAGIC 由 MP 自动生成并同步。
  • 脱机整集群备份,建议人为为所有 MP/BP 使用同一个 BAK_MAGIC
  • 可通过 DMRMAN SHOW BACKUPSET 核对备份集批次。

恢复整集群前,先核对 BAK_MAGIC,比先记 RMAN 命令更重要。 官方魔数说明

4. 整集群恢复的逻辑顺序

整套集群恢复不是只执行一次 RESTORE。官方定义的逻辑步骤是:

text 复制代码
数据还原
  → 恢复一致性
  → 更新 DPC_MAGIC
  → 更新 DB_MAGIC
  → 修正目标环境注册与网络配置
  → 按 MP、BP、SP 顺序启动验证

其中:

  • 恢复一致性:可按备份集恢复,也可利用归档恢复至指定时间;
  • 更新 DPC_MAGIC:整套集群恢复特有,使目标集群与源集群区分;
  • 更新 DB_MAGIC:使每个恢复后的数据库实例具备新的实例标识。

UPDATE DPC_MAGIC 必须早于 UPDATE DB_MAGIC官方整套集群还原说明

5. 归档为什么仍然重要

归档备份可联机或脱机进行,归档还原只能脱机进行。用完全备份加归档,可将 MP 与 BP 恢复到指定时间点。

**指定时间点恢复的前置条件:**日常运行期间必须启用并合理设置 DPC_LOG_INTERVAL。该参数由 MP 定时通知各节点生成同一时间点的日志;只有满足这一前置条件,才能通过同一个 UNTIL TIME 目标保证 MP 与 BP 恢复后的事务一致性。恢复前应先确认历史归档覆盖目标时间点。

例如:

sql 复制代码
RECOVER DATABASE '<dm.ini>'
  WITH ARCHIVEDIR '<归档目录>'
  UNTIL TIME 'YYYY-MM-DD HH:MI:SS';

这种能力是 RAFT 副本本身不能代替的:副本会复制已提交的误删或错误更新,但备份与归档可以提供回退依据。指定时间恢复时,所有 MP 与 BP 必须使用同一恢复时间边界,并配合 DPC_LOG_INTERVAL;否则不能保证集群事务一致。 官方归档恢复示例

6. 两个实操原则

  1. 联机整集群备份期间,若 MP 或 BP 已故障、或备份过程中发生节点故障,备份不允许开始或会失败;因此应先检查集群健康。
  2. 联机增量备份期间,MP 会暂停提交请求以保证 MP 与 BP 的事务一致;虽然理论上很短,仍建议安排在低峰期。 官方联机备份限制

本节最终记忆:

RAFT 让系统"故障后尽量不停";备份归档让系统"发生错误后仍可回到正确状态"。

第11部分:日常运维与故障处置框架

DMDPC 故障处理的第一原则是:

先确认"哪个层级不可用",再看状态与日志;自动恢复尚在进行时,不要急于手工切换或重建。

1. 每日健康检查的最小集合

业务与全局视角,在 SP 上检查:

sql 复制代码
SELECT * FROM V$INSTANCE;
SELECT * FROM V$GLOBAL_RAFT_INFO;
SELECT * FROM DPC_INSTANCE;

重点确认:

  • MP、SP、BP 是否均处于正常服务状态;
  • 每个多副本 RAFT 组是否有一个 Leader;
  • Leader 是否为 PRIMARY / OPEN
  • Follower 是否为 STANDBY / OPEN
  • 在存在写入负载或受控探针事务时,C_SEQNOC_LSN 是否推进且各副本差距是否可接受;空闲期不应仅因位点不推进而判定异常;
  • 是否出现异常节点完全不在 V$GLOBAL_RAFT_INFO 中。

V$GLOBAL_RAFT_INFO 仅显示可正常通信的节点;一个 RAFT 组没有有效主库时,该组信息不会显示。 官方动态视图说明

针对某一个 MP/BP 多副本组,再直连节点检查:

sql 复制代码
SELECT * FROM V$RLOG_RAFT_INFO;
SELECT * FROM V$RAFT_SWITCH_INFO;

V$RLOG_RAFT_INFO 可看到当前角色、Leader、任期、已提交日志 C_SEQ/C_LSN、刷盘进度以及是否日志堆积;V$RAFT_SWITCH_INFO 保存历史切换记录。 官方 RAFT 视图说明

2. 建议的故障排查顺序

#mermaid-svg-rwJq5bolvlfhmSUP{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rwJq5bolvlfhmSUP .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rwJq5bolvlfhmSUP .error-icon{fill:#552222;}#mermaid-svg-rwJq5bolvlfhmSUP .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rwJq5bolvlfhmSUP .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rwJq5bolvlfhmSUP .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rwJq5bolvlfhmSUP .marker.cross{stroke:#333333;}#mermaid-svg-rwJq5bolvlfhmSUP svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rwJq5bolvlfhmSUP p{margin:0;}#mermaid-svg-rwJq5bolvlfhmSUP .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-rwJq5bolvlfhmSUP .cluster-label text{fill:#333;}#mermaid-svg-rwJq5bolvlfhmSUP .cluster-label span{color:#333;}#mermaid-svg-rwJq5bolvlfhmSUP .cluster-label span p{background-color:transparent;}#mermaid-svg-rwJq5bolvlfhmSUP .label text,#mermaid-svg-rwJq5bolvlfhmSUP span{fill:#333;color:#333;}#mermaid-svg-rwJq5bolvlfhmSUP .node rect,#mermaid-svg-rwJq5bolvlfhmSUP .node circle,#mermaid-svg-rwJq5bolvlfhmSUP .node ellipse,#mermaid-svg-rwJq5bolvlfhmSUP .node polygon,#mermaid-svg-rwJq5bolvlfhmSUP .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-rwJq5bolvlfhmSUP .rough-node .label text,#mermaid-svg-rwJq5bolvlfhmSUP .node .label text,#mermaid-svg-rwJq5bolvlfhmSUP .image-shape .label,#mermaid-svg-rwJq5bolvlfhmSUP .icon-shape .label{text-anchor:middle;}#mermaid-svg-rwJq5bolvlfhmSUP .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-rwJq5bolvlfhmSUP .rough-node .label,#mermaid-svg-rwJq5bolvlfhmSUP .node .label,#mermaid-svg-rwJq5bolvlfhmSUP .image-shape .label,#mermaid-svg-rwJq5bolvlfhmSUP .icon-shape .label{text-align:center;}#mermaid-svg-rwJq5bolvlfhmSUP .node.clickable{cursor:pointer;}#mermaid-svg-rwJq5bolvlfhmSUP .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-rwJq5bolvlfhmSUP .arrowheadPath{fill:#333333;}#mermaid-svg-rwJq5bolvlfhmSUP .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-rwJq5bolvlfhmSUP .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-rwJq5bolvlfhmSUP .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rwJq5bolvlfhmSUP .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-rwJq5bolvlfhmSUP .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rwJq5bolvlfhmSUP .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-rwJq5bolvlfhmSUP .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-rwJq5bolvlfhmSUP .cluster text{fill:#333;}#mermaid-svg-rwJq5bolvlfhmSUP .cluster span{color:#333;}#mermaid-svg-rwJq5bolvlfhmSUP div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-rwJq5bolvlfhmSUP .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-rwJq5bolvlfhmSUP rect.text{fill:none;stroke-width:0;}#mermaid-svg-rwJq5bolvlfhmSUP .icon-shape,#mermaid-svg-rwJq5bolvlfhmSUP .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-rwJq5bolvlfhmSUP .icon-shape p,#mermaid-svg-rwJq5bolvlfhmSUP .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-rwJq5bolvlfhmSUP .icon-shape .label rect,#mermaid-svg-rwJq5bolvlfhmSUP .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-rwJq5bolvlfhmSUP .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-rwJq5bolvlfhmSUP .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-rwJq5bolvlfhmSUP :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 业务报错或性能异常
SP:集群实例是否可见?
MP:是否有可用主库?
BP:每个数据RAFT组是否有 Primary / OPEN?
RAFT:选主、日志提交、归档是否正常?
通信/磁盘/归档/配置/多数派逐项定位
自动恢复观察;必要时手工切换或重建

实际检查时,建议按下面四层理解:

  1. 接入层:SP 是否在线;客户端是否可切到其他 SP。
  2. 控制层:MP RAFT 是否存在主库,MP 配置是否一致。
  3. 数据层 :每个 BP RAFT 组是否均有 Primary / OPEN
  4. 复制层:是否有通信失败、日志堆积、归档中断、未完成重演或多数派丢失。

3. 常见现象与优先动作

现象/错误 优先判断 官方建议方向
-518 无效的系统状态 Leader/Follower 的模式与状态 Leader 非 PRIMARY/OPEN 或 Follower 非 STANDBY/OPEN;仅在多数派、网络和归档正常且状态正在收敛时先观察
-739 任期号不匹配 是否正在新一轮选举 多数派、网络和归档恢复正常时可等待重新选举;否则按选举失败的原因排查,不应无限期等待
-738 重演未完成 多数副本是否存活 多数副本正常时等待自动重演;否则先恢复多数派
-718 归档日志不连续 归档是否被覆盖、损坏 归档丢失可能需要重建备库
-650/-653/-654 XLNK 与端口/实例状态 查归档配置、端口占用、服务器状态
-9713/-9729 副本初始化基线 多半未用同一备份集初始化,需要按同源备份重新搭建

以上错误码对应的官方处理方向见 多副本常见错误与处理

4. 何时能手工切换主备

手工切换适合计划维护或明确的主库迁移,不应当作为"出现任何故障后的第一动作"。

目标备库必须满足:

  • 多副本环境;
  • 自身角色为 FOLLOWER
  • 自身为 STANDBY / OPEN
  • 存在有效主库;
  • 自身归档有效;
  • 没有正在进行的节点变更。

标准流程:

sql 复制代码
-- 直连当前主库
SP_RAFT_SUSPEND_THREAD();

-- 直连目标备库
SP_RAFT_SWITCHOVER();

如果第二步失败,应在任一备库执行:

sql 复制代码
SP_RAFT_RESUME_THREAD('<原主库实例名>');

恢复原主库被挂起的工作线程。切换操作要求直连 MP 或 BP;BS 主库需要 LOCAL 方式登录。 官方手工切换流程

5. 三条重要的运维红线

  • 不要把 -738 重演未完成 立即当作需重建的故障;多数派正常时先等待自动恢复。
  • 不要在没有判断多数派状态前操作节点变更或手工切换;日志无法提交时,变更本身无法完成。
  • 不要直连 MP/BP 执行常规业务 SQL;常规业务入口仍应为 SP,MP/BP 直连用于监控与维护。

6. 计划维护:SP 可以灰度升级

SP 支持灰度升级。将某个 SP 标记为升级状态后:

  • 不再接受新连接;
  • 不再被选作计算节点;
  • 使用服务名连接的会话会在事务提交或回滚时平滑切换到其他正常 SP;
  • 所有会话结束后,该 SP 自行退出。
sql 复制代码
CALL SP_SET_SP_UPGRADE('SP1');

升级完成后恢复:

sql 复制代码
CALL SP_RESET_SP_UPGRADE('SP1');

前提是业务连接使用服务名;否则会话只会断开,不能完成预期切换。 官方灰度升级与引流说明

这一部分的最终目标,是形成固定习惯:先看全局 RAFT 健康,再看组内日志与归档,最后才动切换/重建。

第12部分:容量扩展与数据迁移

先给结论:新增 BP 只增加"可用存储与计算资源",不会自动把旧数据搬过去。

要真正均衡负载,必须先为新 BP 创建用户表空间,再按表空间、分区或分区组执行迁移。

官方明确说明:扩缩容后需要手工迁移数据以重新均衡;最推荐的方式是联机表空间迁移官方数据迁移说明

1. 为什么扩容后旧数据不自动重分布

DMDPC 的数据定位主要依赖"子表 → 表空间 → BP RAFT 组"。

新增 BP 后:

  • 新 BP 还没有用户表空间,就不会承载用户数据;
  • 即使创建了表空间,已有表的已有子表仍然留在原表空间;
  • 对间隔分区表,后续自动新建分区也会沿用建表时已固化的表空间集合,不会自动使用后来新增的表空间。

因此,"扩容成功"与"容量已经均衡"是两件不同的事。 官方表空间与建表规则

2. 一个扩容决策流程(概要)

以新增一个 BP RAFT 组为例:

  1. 按既有拓扑注册、启动新 BP(多副本时先完成副本组部署与选主;成员变更前确认多数派正常、日志可提交,且没有未完成的节点变更);
  2. 在新 BP RAFT 组创建一个或多个用户表空间;
  3. 让后续新建表/新建分区可选择这些表空间;
  4. 评估原有表空间的容量、热点和表对象归属;
  5. 将适合迁移的表空间迁到新 RAFT 组;
  6. 验证数据位置、容量和业务性能。

一个BP RAFT组可承载多个表空间;一个表空间的逻辑存储位置只属于一个BP RAFT组,多副本时该组的普通副本各自保存对应物理文件。新增BP RAFT组后,至少要创建一个用户表空间才会用于用户数据存储。 官方表空间迁移说明

3. 三种迁移方式怎么选

方式 粒度 业务影响 典型用途
表空间迁移 整个表空间 联机方式影响较小 扩容后的常规均衡,推荐
分区迁移 一个分区/子分区 迁移期间该表不支持读写 精确调整局部热点
分区组迁移 一张分区表 迁移整表 同一分区规则下整体换位置
库迁移 一个节点库 需要停机 特殊搬迁,不推荐作常规扩容

表空间是 DMDPC 数据迁移的常用单位:一个表空间内的多个对象可一次迁移,不需要按行重新计算分布。 官方迁移方式比较

4. 推荐:联机表空间迁移

语法核心:

sql 复制代码
ALTER TABLESPACE <表空间名>
  MOVE TO <目标BP_RAFT组>
  [READ ONLY | NOT READ ONLY];

例如:

sql 复制代码
-- 默认或显式只读迁移
ALTER TABLESPACE TS_01 MOVE TO RAFT_2 READ ONLY;

-- 尽量保持业务可写
ALTER TABLESPACE TS_01 MOVE TO RAFT_2 NOT READ ONLY;

两种模式的差异:

  • READ ONLY:迁移期间该表空间中的表只能读,不能写;未指定时默认这一模式。
  • NOT READ ONLY:文件拷贝期间仍可读写,迁移过程中产生的新日志持续重演;只在最终切换的短时间阻塞写操作。该模式要求 MP 与 BP 已配置归档。

不能将表空间迁往含有故障节点的 RAFT 组。 官方联机表空间迁移

迁移过程中可观察:

sql 复制代码
SELECT * FROM V$DPC_TS_MOVE;

重点关注源/目标 RAFT、持续时间、文件复制和日志重演进度。 官方迁移进度视图

5. 需要精确移动时:分区或整表

未使用分区组建表的分区表,可移动一个分区:

sql 复制代码
ALTER TABLE T_ORDER MOVE PARTITION P2026Q1 TO TS_NEW;

可加 FAST 提升效率,但 FAST 方式不处理约束冲突数据,约束正确性需要由操作者保证;普通方式更稳妥。 官方分区迁移

使用分区组建表的表,支持按分区组迁移整表:

sql 复制代码
ALTER TABLE T_ORDER MOVE TO PG_NEW;

源、目标分区组除了存储位置外,分区方式与分区数等定义必须一致。它本质上是通过换分区组,改变整张表各分区所在的 RAFT 组。 官方基于分区组的整表迁移

6. 扩容时的建模建议

若多张大表经常按同一业务键关联:

  1. 用相同分区键、相同分区数创建分区组;
  2. 让这些表使用同一个分区组;
  3. 扩容后迁移时,也保持各表对应分区的同位关系。

这样能减少跨机事务和数据交换,更容易形成 Partition Wise Join。 官方分区组与 PWJ 说明

本节要建立的运维观念是:

扩容不是"加节点",而是"加节点 + 加表空间 + 数据迁移 + 验证均衡"。

第13部分:DMDPC 性能调优方法论

DMDPC 的性能问题,优先按这个顺序处理:

数据分布 → 跨节点交换 → 分区裁剪 → 并行与倾斜 → 单节点执行细节 → 最后才是 HINT。

因为单机 SQL 即使索引、连接算法都合理,只要在 DMDPC 中产生大规模 ESEND/ERECV 数据交换,性能仍可能很差。

1. 首先看执行计划里的交换

从 SP 执行:

sql 复制代码
EXPLAIN <SQL>;

DMDPC 的执行计划在单机计划基础上插入 ESEND/ERECV

  • ESEND:发送子任务结果;
  • ERECV:接收子任务结果;
  • 二者成对出现,标志着一个子计划边界;
  • 常见于连接、分组、排序、去重。

优先问三个问题:

  1. 这个交换是否必要?
  2. 被交换的数据量是否过大?
  3. 是否本可利用同分布数据而避免交换?

优化器会在广播、双边重分发、汇聚等路径之间做代价选择。 官方计划与交换机制

计划信号 常见含义 优先优化方向
BROADCAST 小表被复制到多个执行端 确认被广播侧确实足够小
BY HASH / N_DEST 按键重分发 检查连接键、分区键是否匹配
DIRECT 定向发送/汇聚 检查是否形成单点瓶颈
双边 DIS 连接两侧均发生重分发 优先考虑同分区建模与 PWJ

2. 用分区组争取 PWJ

如果两张大表经常按同一业务键关联,应尽量:

  • 使用相同的分区键、分区方式和分区数;
  • 使用相同分区组,确保对应分区落在同一表空间/BP;
  • 在连接条件中使用分区键。

这样可以触发分区智能连接(PWJ),减少甚至避免跨节点数据交换。

注意:PWJ 不只是"分区键一样"就够了,对应分区的物理 BP 位置也必须一致。 官方分区组与 PWJ 说明

3. 看 GI:有没有真正做到分区裁剪

计划中的 GI 控制扫描粒度。重点关注:

  • 是否扫描了不该扫描的分区;
  • 是否出现静态或动态分区裁剪;
  • 是否因参数化条件、等值连接而在运行时裁剪;
  • PWJ 场景下的并行度是否足够。

官方文档指出,分区列过滤可触发裁剪;动态裁剪信息可从计划附加信息 dynamic_pll 观察。PWJ 对 MAX_PARALLEL_DEGREE 也有要求:不应小于每个 BP 上的水平分区子表数。 官方 GI 与分区裁剪说明

4. 别只看总耗时,要看"哪个线程拖尾"

启用:

ini 复制代码
ENABLE_MONITOR = 1

然后针对慢 SQL 的执行号查询:

sql 复制代码
SELECT *
FROM V$DPC_STASK_THRD
WHERE EXEC_ID = <执行号>;

重点比较同一 STASK_NO 下不同 THRD_NO 的:

  • TIME_USED:是否有明显慢线程;
  • N_ROWS_SEND / N_BYTES_SEND:是否输出量悬殊;
  • N_ROWS_RECV:是否某些接收端压力明显更大;
  • P_WAIT_TIMES:开启限流时,是否有发送端持续等待;
  • FIRST_ROW_USED:首行慢还是整体吞吐慢。

同一个子任务中某个线程数据量或耗时远高于其他线程,通常意味着数据倾斜、分区设计不均,或分发键存在热点。 官方子任务线程视图

5. 统计信息必须可信

DMDPC 优化器基于代价选择分布方式,而代价高度依赖统计信息与数据分布。出现以下情况时,计划容易偏离最优:

  • 统计信息缺失或陈旧;
  • 抽样不能反映真实倾斜;
  • 数据规模、NDV、热点分布变化明显;
  • 表空间迁移、批量装载后未及时更新统计信息。

因此,看到不合理的广播、重分发或并行度前,先验证统计信息,再考虑强制干预。 官方 SQL 调优原则

6. HINT 是最后的精确工具

DMDPC 支持分发方式提示:

sql 复制代码
SELECT /*+ DPC(<nth_try> <分发方式>) */ <select_list>
FROM <table_name>;

其中 <nth_try> 通过 10053 trace 中的 nth_try 获取。可指定广播、重分发、汇聚等有效分发路径。

但要注意:

  • HINT 只在指定路径有效且仅因代价未被选中时才可能生效;
  • 写错语法或不可用时,DM 通常会忽略 HINT,不一定报错;
  • HINT 不能弥补错误的分区设计或失真的统计信息。

因此它适合"已定位到某个分发决策错误、且已验证替代路径更好"的场景。 官方 DPC HINT 说明

最终可把一次慢 SQL 排查压缩为:

text 复制代码
EXPLAIN
→ 看 ESEND/ERECV 交换
→ 看 GI 与分区裁剪
→ 看同分区/PWJ 机会
→ 用 V$DPC_STASK_THRD 找线程倾斜
→ 校准统计信息与数据分布
→ 必要时用 10053 + DPC HINT 验证
相关推荐
潘正翔1 小时前
docker核心概念
linux·运维·服务器·docker·容器·centos·运维开发
韩楚风1 小时前
【参天引擎】事务生命周期 / MVCC / Undo / ACID / 分布式事务 功能域整体解析
数据库·分布式·mysql·架构·cantian
通问AI2 小时前
Apple Intelligence 国行备案深度技术解析:阿里千问如何被集成到苹果端侧AI架构
人工智能·架构
明航咨询—张老师2 小时前
软考软件评测师技术深度剖析:2026年考试改革后的知识体系与备考架构
大数据·架构
renhongxia12 小时前
世界模型,是“空中楼阁”还是AGI的“最后一块拼图”?
运维·服务器·数据库·人工智能·算法·agi
程序员无隅2 小时前
Coding Agent 为什么压缩上下文后还能继续工作?上下文模块设计拆解
java·开发语言·数据库
愿做无知一猿3 小时前
Nacos连接MySQL异常?DataGrip竟成救星
数据库·mysql
G.O.G.O.G3 小时前
LeetCode SQL 从入门到精通(MySQL)06(上)
数据库·sql·mysql·leetcode
研究员子楚3 小时前
GEO行业发展标准体系白皮书V2.0-第10卷 · 全球篇:跨国标准协同与全球品牌语义治理框架
数据库·人工智能·microsoft·架构·geo