
适用版本:TDengine v3.x(v3.3.x / v3.4.x) | 最后更新:2026-05-14
概述
MNode(Management Node)是 TDengine 集群的"大脑",负责管理所有元数据和协调集群操作。它是一个运行在某些 dnode 上的逻辑节点,最多可以有 3 个副本通过 Raft 协议保持一致性。
本文深入解析 MNode 的四大核心机制:
- SDB(System Database):内存中的元数据存储引擎,持久化集群所有元信息
- 事务引擎:所有 DDL 操作的原子性保障,支持前进重试和回滚恢复
- DDL 处理流程:一条 DDL 从接收到执行完成的完整链路
- 定时器系统:驱动心跳检测、事务重试、流计算检查等周期性任务
核心概念速查表
| 概念 | 说明 |
|---|---|
| SDB | System Database,MNode 内部的元数据键值存储引擎 |
| SDB 表 | SDB 中按类型划分的元数据集合(如 dnode 表、database 表、超级表表等) |
| SDB Raw | 元数据的序列化二进制格式,用于持久化和 Raft 复制 |
| 事务(Trans) | DDL 操作的原子执行单元,包含 redo/undo/commit 三类动作列表 |
| 事务阶段 | 事务执行的状态机,从 PREPARE → REDO → COMMIT → FINISH |
| 冲突检测 | 防止多个事务并发修改同一资源(数据库级或全局级) |
| vgId=1 | MNode 的 Raft 组固定使用 vgId=1 |
| sdb.dat | SDB 全量快照文件,存储在 MNode 数据目录下 |
详细解析
1. SDB --- 元数据存储引擎
1.1 架构定位
SDB 是 MNode 的核心存储层,所有集群元数据(节点信息、数据库定义、超级表 Schema、VGroup 分布、用户权限、流计算规则等)都存储在 SDB 中。
SDB 架构:
┌──────────────────────────────────────────────────┐
│ MNode 进程 │
│ │
│ ┌───────────────────────────────────────────┐ │
│ │ SDB(内存) │ │
│ │ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │ dnode 表 │ │ db 表 │ │ stb 表 │ ... │ │
│ │ │(哈希表) │ │(哈希表) │ │(哈希表) │ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │
│ └──────────────┬────────────────────────────┘ │
│ │ │
│ ┌────────┼────────┐ │
│ ▼ ▼ ▼ │
│ sdb.dat WAL 日志 Raft 复制 │
│ (全量快照) (增量日志) (多副本同步) │
└──────────────────────────────────────────────────┘
设计特点:
- 纯内存操作:所有元数据读取直接从内存哈希表查找,无磁盘 I/O
- 按类型分表:每种元数据类型一个独立的哈希表,支持不同的键类型(整数键或字符串键)
- 引用计数:读取元数据时获取引用,使用完毕后释放,防止读取到正在修改的数据
- 读写锁:每张 SDB 表有独立的读写锁,读操作并发执行,写操作互斥
1.2 SDB 表类型全览
SDB 内部包含约 30 张元数据表,覆盖集群管理的所有维度:
| 分类 | SDB 表 | 键类型 | 存储内容 |
|---|---|---|---|
| 集群管理 | cluster | INT64 | 集群 ID、名称、运行时间 |
| dnode | INT32 | 数据节点注册信息、硬件资源、在线状态 | |
| mnode | INT32 | 管理节点副本信息、Raft 角色 | |
| qnode | INT32 | 查询节点 | |
| snode | INT32 | 流计算节点 | |
| anode | INT32 | AI 分析节点 | |
| 数据管理 | db | STRING | 数据库定义、全部配置参数 |
| vgroup | INT32 | VGroup 分布、哈希范围、副本位置、负载统计 | |
| stb | STRING | 超级表 Schema(列定义、Tag 定义、压缩配置) | |
| sma | STRING | SMA 索引定义(RSMA/TSMA) | |
| idx | STRING | 自定义索引 | |
| 用户权限 | user | STRING | 用户名、密码哈希、权限映射 |
| auth | STRING | 认证记录 | |
| acct | STRING | 账户信息、资源配额 | |
| 订阅系统 | topic | STRING | TMQ 主题定义(SQL/数据库/超级表订阅) |
| consumer | INT64 | 消费者状态、分配的主题 | |
| subscribe | STRING | 订阅关系 | |
| offset | STRING | 消费位点 | |
| 流计算 | stream | STRING | 流计算任务定义 |
| stream_ck | --- | 流计算检查点 | |
| stream_seq | --- | 流计算序列号 | |
| 事务 | trans | INT32 | 事务记录(用于崩溃恢复) |
| 其他 | func | STRING | UDF 函数定义 |
| view | STRING | 视图定义 | |
| arbgroup | INT32 | 仲裁组信息 | |
| compact | INT32 | 压缩任务 | |
| cfg | STRING | 动态配置 | |
| grant | --- | 授权日志 |
1.3 行的生命周期
SDB 中每行数据都有一个状态字段,反映其在事务中的生命周期位置:
创建流程: INIT → CREATING → READY(正常服务)
删除流程: READY → DROPPING → DROPPED
更新流程: READY → UPDATE → READY
- INIT / CREATING:事务 PREPARE 阶段写入,表示正在创建中
- READY:事务 COMMIT 阶段写入,表示可正常使用
- DROPPING / DROPPED:删除事务的中间和最终状态
- UPDATE:更新事务的中间状态
这种多阶段状态设计的好处是:事务中途失败时,可以根据行状态判断需要回滚还是前进。例如,状态为 CREATING 的行在回滚时直接删除即可。
1.4 持久化与复制
SDB 数据通过两种方式持久化:
WAL(Write-Ahead Log):
- MNode 的 Raft 组(vgId=1)使用 WAL 记录每一条元数据变更
- WAL 采用
fsync模式强制刷盘,保证不丢数据 - WAL 是增量日志,用于 Raft 日志复制和故障恢复
sdb.dat(全量快照):
- 周期性地将整个 SDB 内存数据序列化写入
sdb.dat文件 - 写入有增量阈值控制------只有累积了足够多的变更才会触发写入
- 启动时先加载
sdb.dat,再回放 WAL 中尚未包含在快照中的日志
Raft 复制:
- 所有 SDB 写入操作先封装为二进制格式,通过 Raft 协议提议(propose)
- Leader 将提议复制到多数派 Follower 后,才将变更应用到内存
- Follower 的 SDB 通过回放 Raft 提交的日志来保持与 Leader 一致
- 当 Follower 落后过多时,Leader 发送完整 SDB 快照(snapshot)来同步
2. 事务引擎
2.1 为什么需要事务?
一个简单的 CREATE DATABASE 操作,实际上需要在多个节点上执行多个步骤:
- 在 SDB 中写入数据库元数据
- 在 SDB 中写入多个 VGroup 元数据
- 向多个 dnode 发送消息,要求它们创建对应的 vnode
- 确认所有 vnode 创建成功后,将元数据状态从 CREATING 改为 READY
如果步骤 3 中某个 dnode 不可达,整个操作需要原子性地成功或失败。这就是事务引擎的职责。
2.2 事务的构成
每个事务包含以下关键属性:
| 属性 | 说明 |
|---|---|
| 事务 ID | 自增的唯一整数标识 |
| 当前阶段 | 事务状态机的当前位置(见下文) |
| 冲突类型 | 决定与其他事务的冲突检测范围 |
| 执行策略 | 失败时重试还是回滚 |
| 执行模式 | 动作并行还是串行执行 |
| 动作列表 | 四组动作列表:prepare / redo / undo / commit |
每个"动作"要么是一条 SDB 写入 (修改元数据),要么是一条 RPC 消息(发往 vnode 或 dnode)。
2.3 事务阶段状态机
事务阶段流转:
成功
PREPARE ──────→ REDO_ACTION ──────→ COMMIT ──────→ COMMIT_ACTION ──────→ FINISH
│ │ ▲
│ │ 失败 │
│ ▼ │
│ ROLLBACK ────→ UNDO_ACTION ──────→ PRE_FINISH ─────────────┘
│
└──→ 失败:直接结束(Raft 未提交,无需回滚)
各阶段说明:
| 阶段 | 说明 |
|---|---|
| PREPARE | 将事务记录和 prepare 动作通过 Raft 提议。Raft 提交后,事务持久化到多数派节点,保证即使 Leader 切换也不会丢失 |
| REDO_ACTION | 执行前进动作------向 dnode/vnode 发送创建/修改消息,或写入 SDB 记录。所有动作完成后进入 COMMIT |
| COMMIT | 将 commit 动作列表中的 SDB 写入通过 Raft 提交,将元数据状态从中间态更新为最终态(如 CREATING → READY) |
| COMMIT_ACTION | 执行提交后的善后动作(如果有的话) |
| FINISH | 事务完成,从 SDB 中删除事务记录 |
| ROLLBACK | redo 失败且策略为回滚时的过渡阶段 |
| UNDO_ACTION | 执行回滚动作------向 dnode/vnode 发送删除/撤销消息,将 SDB 中间态记录清理 |
| PRE_FINISH | 回滚完成后的清理阶段 |
2.4 执行策略
事务创建时选择失败时的应对策略:
| 策略 | 行为 | 适用场景 |
|---|---|---|
| 重试(Retry) | redo 动作失败时,等待定时器周期性重试,直到成功为止 | CREATE DATABASE、CREATE STABLE 等大多数 DDL------目标节点可能临时不可达,重试即可 |
| 回滚(Rollback) | redo 动作失败时,执行 undo 动作列表撤销所有已完成的操作 | 某些对一致性要求更严格的操作 |
重要 :采用重试策略的事务会无限重试(直到手动 KILL 或成功为止)。如果某个 dnode 永久下线,相关事务会一直处于 REDO_ACTION 阶段,阻塞同一冲突范围内的其他事务。此时需要管理员介入。
2.5 执行模式
| 模式 | 行为 |
|---|---|
| 并行(Parallel) | 所有 redo/undo 动作同时发出,不等待前一个完成 |
| 串行(Serial) | 动作逐个执行,前一个完成后才发送下一个 |
| 分组并行(Group Parallel) | 同一组内并行,不同组之间串行 |
大多数 DDL 使用并行模式------例如 CREATE DATABASE 需要向多个 dnode 发送创建 vnode 的消息,这些消息可以同时发出。
2.6 冲突检测
为了防止并发 DDL 操作导致元数据不一致,事务引擎实施冲突检测:
| 冲突类型 | 范围 | 示例 |
|---|---|---|
| 无冲突 | 不与任何事务冲突 | 只读操作 |
| 全局冲突 | 与所有其他事务互斥 | CREATE MNODE、DROP DNODE |
| 数据库级冲突 | 与同一数据库的事务互斥 | CREATE DATABASE、ALTER DATABASE |
| 数据库内冲突 | 与同一数据库内同一超级表的事务互斥 | CREATE STABLE、ALTER STABLE |
当新事务提交时,先检查是否有冲突的进行中事务。如果有冲突,返回错误码 TSDB_CODE_MND_TRANS_CONFLICT,客户端需要稍后重试。
2.7 事务的崩溃恢复
事务本身也是 SDB 中的一张表(trans 表),通过 Raft 复制到多数派节点。这意味着:
- Leader 切换:新 Leader 上任后,从 SDB 中读取所有未完成的事务,自动继续执行
- MNode 重启 :从
sdb.dat+ WAL 恢复 SDB 后,定时器自动拉起(pullup)未完成的事务 - 动作超时:每个动作有 15 分钟的超时限制,超时后标记失败并触发策略(重试或回滚)
定时器每 2 秒 (tsTransPullupInterval)检查一次是否有需要推进的事务。
2.8 管理事务
sql
-- 查看当前正在执行的事务
SHOW TRANSACTIONS;
-- 输出示例:
-- id | create_time | stage | db | type | lasted
-- 5 | 2024-01-15 10:30:00 | redoAction | power | create-db | 120s
-- 强制终止卡住的事务(跳过未完成的动作)
KILL TRANSACTION 5;
KILL TRANSACTION 有两种模式:
- 跳过模式:将所有未完成的动作标记为"已完成",让事务直接走完剩余阶段
- 中断模式:直接跳到 PRE_FINISH 阶段结束事务
警告:KILL TRANSACTION 是危险操作------它可能导致部分 dnode 上的 vnode 与 SDB 元数据不一致。只有在确认事务无法自行恢复时才使用,使用后需要检查集群状态。
3. DDL 处理全链路
3.1 总体流程
以 CREATE DATABASE power VGROUPS 4 REPLICA 3 为例,展示 DDL 的完整处理流程:
DDL 全链路(以 CREATE DATABASE 为例):
客户端 MNode Leader DNode 1/2/3
│ │ │
│ 1. 发送 CREATE DB 请求 │ │
│─────────────────────────→│ │
│ │ │
│ │ 2. 反序列化,提取参数 │
│ │ 3. 检查:DB 是否已存在? │
│ │ 4. 检查:用户权限 │
│ │ 5. 检查:授权/License │
│ │ 6. 校验 DB 配置参数 │
│ │ │
│ │ 7. 分配 VGroup: │
│ │ 计算各 dnode 评分 │
│ │ 选择评分最低的 dnode │
│ │ 分配 hash 范围 │
│ │ │
│ │ 8. 创建事务(重试策略,DB 级冲突)│
│ │ │
│ │ 9. 构建动作列表: │
│ │ prepare: DB+VGroup 写入 SDB(CREATING 状态)
│ │ redo: 向 dnode 发消息创建 vnode
│ │ undo: 向 dnode 发消息删除 vnode
│ │ commit: DB+VGroup 写入 SDB(READY 状态)
│ │ │
│ │ 10. PREPARE 阶段: │
│ │ 通过 Raft 提议事务记录 │
│ │ Raft 多数派确认后, │
│ │ DB 和 VGroup 以 CREATING │
│ │ 状态出现在 SDB 中 │
│ │ │
│ 11. 返回"操作进行中" │ │
│←─────────────────────────│ │
│ │ │
│ │ 12. REDO 阶段: │
│ │ 向 dnode 1/2/3 发送 │
│ │ 创建 vnode 消息(并行) │
│ │─────────────────────────────→│
│ │ │
│ │ 13. 收到所有 dnode 的成功响应 │
│ │←─────────────────────────────│
│ │ │
│ │ 14. COMMIT 阶段: │
│ │ 通过 Raft 提议: │
│ │ DB → READY │
│ │ VGroup → READY │
│ │ │
│ │ 15. FINISH:删除事务记录 │
│ │ │
│ 16. 异步通知客户端成功 │ │
│←─────────────────────────│ │
3.2 不同 DDL 的事务构成
| DDL 操作 | 冲突级别 | 执行策略 | Redo 动作 | Commit 动作 |
|---|---|---|---|---|
CREATE DATABASE |
DB 级 | 重试 | 向 dnode 发消息创建 vnode | DB+VGroup → READY |
DROP DATABASE |
DB 级 | 重试 | 向 dnode 发消息删除 vnode | DB+VGroup → DROPPED |
ALTER DATABASE |
DB 级 | 重试 | 向 dnode 发消息更新 vnode 配置 | DB → READY(新配置) |
CREATE STABLE |
DB 内 | 重试 | 向 vnode 发消息创建超级表 | STB → READY |
ALTER STABLE |
DB 内 | 重试 | 向 vnode 发消息更新 Schema | STB → READY(新 Schema) |
DROP STABLE |
DB 内 | 重试 | 向 vnode 发消息删除超级表 | STB → DROPPED |
CREATE USER |
全局 | 回滚 | SDB 写入用户记录 | User → READY |
CREATE MNODE |
全局 | 重试 | 向目标 dnode 发消息创建 mnode | MNode → READY |
DROP DNODE |
全局 | 重试 | 迁移该节点上的所有 vnode | DNode → DROPPED |
BALANCE VGROUP |
DB 级 | 重试 | 向 dnode 发消息迁移 vnode | VGroup → READY(新分布) |
3.3 错误处理与重试
当 redo 动作(RPC 消息)失败时:
动作失败处理:
动作发送 → dnode 响应失败(超时/拒绝/不可达)
│
├── 策略=重试:
│ 标记动作为"未完成"
│ 事务保持在 REDO_ACTION 阶段
│ 等待下一次定时器触发(每 2 秒)
│ 重新发送未完成的动作
│ (无限重试,直到成功或被 KILL)
│
└── 策略=回滚:
事务进入 ROLLBACK → UNDO_ACTION
发送 undo 动作撤销已完成的 redo
所有 undo 完成后 → PRE_FINISH → FINISH
动作超时:每个动作有 15 分钟的超时限制。超时后自动标记为失败,触发重试或回滚。
4. MNode 部署与高可用
4.1 MNode 副本
MNode 最多 3 个副本,通过 Raft 协议保持一致性:
- vgId=1:MNode 的 Raft 组固定使用 vgId=1
- Leader:处理所有写操作(DDL),协调事务执行
- Follower:被动接收 Raft 日志,保持元数据同步
- 只读转发:Follower 收到写请求时,返回 Leader 地址,客户端自动重定向
sql
-- 查看 MNode 状态
SHOW MNODES;
-- 输出示例:
-- id | endpoint | role | role_time | create_time
-- 1 | node1:6030 | leader | 2024-01-15 10:00:00 | 2024-01-01 00:00:00
-- 2 | node2:6030 | follower | 2024-01-15 10:00:00 | 2024-01-02 00:00:00
-- 3 | node3:6030 | follower | 2024-01-15 10:00:00 | 2024-01-03 00:00:00
-- 创建 MNode 副本
CREATE MNODE ON DNODE 2;
CREATE MNODE ON DNODE 3;
-- 删除 MNode 副本
DROP MNODE ON DNODE 3;
4.2 Leader 选举与切换
MNode 使用 Raft 协议进行 Leader 选举:
- 正常运行:Leader 定期向 Follower 发送心跳
- Leader 失联 :Follower 在选举超时(
tsMnodeElectIntervalMs)后发起选举 - 投票:候选者向其他节点发送投票请求,获得多数票者成为新 Leader
- 恢复执行:新 Leader 上任后,自动拉起所有未完成的事务继续执行
4.3 Leader 切换后的恢复
新 Leader 的恢复过程:
- Raft 日志回放 :回放 WAL 中未包含在
sdb.dat快照中的日志条目 - SDB 恢复标记:标记恢复完成,允许 SDB 接受新的写入
- 事务拉起:扫描 SDB 中所有未完成的事务(trans 表),根据各事务当前阶段继续执行
- 状态广播:更新流计算等模块的执行信息
注意:Follower 不执行事务、不处理 DDL、不运行定时任务。所有这些工作都由 Leader 独占。
4.4 快照同步
当 Follower 落后太多(WAL 日志已被回收),Leader 通过快照同步来恢复:
- Leader 将整个 SDB 内存数据序列化为二进制流
- 分块发送给落后的 Follower
- Follower 用收到的快照替换本地 SDB
- 后续通过增量 Raft 日志继续同步
5. 定时器系统
MNode 有两个定时线程,驱动所有周期性任务。只有 Leader 节点执行这些任务,Follower 节点跳过。
5.1 秒级定时器
每 1 秒触发一次,处理以下任务:
| 任务 | 默认间隔 | 说明 |
|---|---|---|
| 事务拉起 | 2 秒 | 检查并推进所有未完成的事务 |
| TMQ 消费者重平衡 | 2 秒 | 检查消费者组是否需要重新分配 |
| 压缩任务检查 | 10 秒 | 检查进行中的压缩任务状态 |
| TTL 过期清理 | 10 秒 | 向 vnode 发送清理过期子表的指令 |
| 流计算检查点 | 30 秒 | 触发流计算状态持久化 |
| 流计算共识 | 30 秒 | 检查流计算跨节点一致性 |
| 授权心跳 | 60 秒 | 检查 License 有效性 |
| 流节点健康检查 | 240 秒 | 检查流计算节点存活状态 |
| 集群运行时间 | 300 秒 | 更新集群 uptime 计数器 |
| VDB 裁剪 | 3600 秒 | 触发 vnode 数据库空间回收 |
| S3 迁移 | 3600 秒 | 触发数据迁移到对象存储 |
| 遥测上报 | 86400 秒 | 发送匿名使用统计(可关闭) |
5.2 毫秒级定时器
每 100 毫秒触发一次,处理需要更高时间精度的任务:
| 任务 | 默认间隔 | 说明 |
|---|---|---|
| 仲裁心跳 | 2000 毫秒 | 向仲裁组发送心跳 |
| 仲裁同步检查 | 3000 毫秒 | 检查仲裁组数据同步状态 |
| DNode 在线检测 | 5000 毫秒 | 检查各 dnode 心跳是否超时 |
| Raft 提议超时检查 | 30 秒 | 检查是否有卡住的 Raft 提议 |
| 快照发送重试 | 可配置 | 重试失败的快照发送 |
5.3 定时器与事务的协作
定时器系统是事务引擎的"心跳驱动"------没有定时器,失败的事务永远不会被重试。协作流程:
定时器驱动事务执行:
每 2 秒 → 扫描 trans 表
│
├── 事务 A(REDO_ACTION 阶段,有未完成动作)
│ → 重新发送未完成的 RPC 消息
│
├── 事务 B(COMMIT 阶段)
│ → 通过 Raft 提议 commit 日志
│
└── 事务 C(FINISH 阶段)
→ 从 SDB 中删除事务记录
6. MNode 初始化顺序
MNode 启动时,按照严格的依赖顺序初始化各个模块:
初始化顺序(依赖关系从上到下):
1. WAL ← 日志存储基础
2. SDB ← 元数据存储引擎
3. Trans ← 事务引擎
4. Cluster ← 集群基本信息
5. MNode / QNode / SNode / ANode ← 逻辑节点管理
6. ArbGroup / Config ← 仲裁和配置
7. DNode ← 数据节点管理
8. User / Grant / Privilege / Acct ← 用户权限体系
9. Stream / Topic / Consumer / Subscribe ← 订阅和流计算
10. VGroup / STB / SMA / Index ← 数据对象
11. InfoSchema / PerfSchema ← 系统表
12. DB ← 数据库管理
13. Func / View / Compact ← 功能模块
14. ← 加载 sdb.dat 恢复持久化数据 →
15. Profile / Show / Query ← 查询和展示
16. Sync ← Raft 协议启动
17. Telemetry ← 遥测(最后启动)
关键点:SDB 的加载(读取 sdb.dat 文件)在模块注册之后、Sync 启动之前完成。这确保了所有表的回调函数都已注册,可以正确反序列化各种类型的元数据。
7. 消息处理机制
7.1 消息分发
MNode 维护一个消息类型到处理函数的映射表。各模块在初始化时注册自己关心的消息类型。收到请求时,MNode 按以下流程处理:
消息处理流程:
RPC 请求到达
│
▼
查找消息类型对应的处理器
│
├── 未注册 → 返回错误
│
▼
检查 MNode 状态
│
├── 未就绪(正在恢复/非 Leader)→ 返回重定向或错误
│
▼
调用处理器执行业务逻辑
│
├── 同步返回 → 直接回复客户端
└── 异步(创建事务)→ 返回"操作进行中"
7.2 消息队列
不同类型的消息进入不同的队列,实现隔离和优先级控制:
| 队列 | 处理内容 |
|---|---|
| 写队列 | DDL 操作(CREATE/ALTER/DROP)、定时器触发的写任务 |
| 读队列 | 元数据查询(SHOW 命令)、遥测、流节点检查 |
| 同步队列 | Raft 协议消息(选举、日志复制、心跳) |
| 应用队列 | Raft 提交的日志条目应用到 SDB |
| 仲裁队列 | 仲裁心跳和同步检查 |
7.3 事务响应处理
当 dnode/vnode 完成事务动作后,会发送响应消息回 MNode。MNode 的处理流程:
- 从响应中提取事务 ID 和动作 ID
- 在 SDB 的 trans 表中查找对应事务
- 将该动作标记为"已完成"
- 检查当前阶段的所有动作是否都已完成
- 如果全部完成,推进事务到下一阶段
代码示例
观察事务执行过程
sql
-- 创建一个大型数据库(多 VGroup),观察事务过程
CREATE DATABASE bigdb VGROUPS 100 REPLICA 3;
-- 立即查看事务状态
SHOW TRANSACTIONS;
-- 可以看到 create-db 事务处于 redoAction 阶段
-- 因为需要向多个 dnode 发送创建 vnode 的消息
-- 等待几秒后再次查看
SHOW TRANSACTIONS;
-- 事务完成后,列表为空
模拟事务冲突
sql
-- 终端 1:执行一个耗时的 DDL
ALTER DATABASE power WAL_RETENTION_PERIOD 3600;
-- 终端 2:同时执行同一数据库的 DDL(会冲突)
ALTER DATABASE power CACHEMODEL 'both';
-- 可能返回错误:Transaction conflict
-- 需要等终端 1 的事务完成后重试
处理卡住的事务
sql
-- 查看是否有长时间未完成的事务
SHOW TRANSACTIONS;
-- 如果某事务的 lasted 时间异常长(如超过 10 分钟)
-- 检查相关 dnode 是否在线
SHOW DNODES;
-- 如果 dnode 已永久下线,需要先处理 dnode
-- 然后考虑 KILL 卡住的事务
KILL TRANSACTION <trans_id>;
-- KILL 后检查集群状态
SHOW power.VGROUPS;
MNode 高可用部署
sql
-- 查看当前 MNode 状态
SHOW MNODES;
-- 在三个不同节点上部署 MNode
CREATE MNODE ON DNODE 2;
CREATE MNODE ON DNODE 3;
-- 验证 3 副本状态
SHOW MNODES;
-- 应该看到 1 个 leader + 2 个 follower
-- 模拟 Leader 故障恢复:
-- 停止 leader 所在节点后,几秒内会自动选出新 leader
-- 集群 DDL 操作短暂不可用后自动恢复
性能考量
事务相关参数
| 参数 | 默认值 | 说明 |
|---|---|---|
tsTransPullupInterval |
2 秒 | 事务重试检查间隔。减小可加快重试速度,但增加 CPU 开销 |
| 动作超时 | 15 分钟 | 单个动作的超时时间。超时后标记失败并重试 |
| Raft 提议超时 | 60 秒 | 通过 Raft 提议的超时时间 |
MNode 选举与心跳参数
| 参数 | 说明 |
|---|---|
tsMnodeElectIntervalMs |
Raft 选举超时。减小可更快检测 Leader 故障,但增加误选风险 |
tsMnodeHeartbeatIntervalMs |
Raft 心跳间隔。与选举超时配合,通常为选举超时的 1/3~1/5 |
SDB 性能特点
| 方面 | 特点 |
|---|---|
| 读取性能 | O(1) 哈希查找,纯内存操作,极快 |
| 写入性能 | 受 Raft 多数派确认延迟限制,通常几毫秒到几十毫秒 |
| DDL 吞吐 | 受事务冲突限制------同一数据库的 DDL 串行执行 |
| 内存占用 | 与元数据总量成正比------百万张表的超级表 Schema 可能占用数 GB |
DDL 性能优化建议
- 避免频繁 ALTER:每次 ALTER STABLE 都需要向所有 vnode 推送新 Schema,VGroup 越多延迟越高
- 合理设置 VGROUPS:VGroup 数量直接影响 CREATE DATABASE 事务的动作数量
- 批量操作 :需要创建大量子表时,使用
INSERT INTO ... USING ... TAGS ...自动建表,而非逐个 CREATE TABLE - 避免 DDL 并发:同一数据库的 DDL 因冲突检测机制而串行,客户端不应并发发送
FAQ
Q1: MNode 和 VNode 的关系是什么?
MNode 负责元数据管理 (数据库定义、表 Schema、用户权限、VGroup 分布等),VNode 负责数据存储和查询。MNode 不存储任何时序数据。客户端写入/查询时先通过 MNode 获取路由信息(表在哪个 VGroup),然后直接与 VNode 通信。
Q2: 为什么 DDL 操作有时候很慢?
DDL 操作需要:(1) 通过 Raft 持久化元数据变更,(2) 向多个 dnode 发送消息并等待响应。如果某个 dnode 负载高或网络延迟大,整个事务会等待。VGroup 数量越多,需要发送的消息越多。可以通过 SHOW TRANSACTIONS 观察事务当前阶段和耗时。
Q3: "Transaction conflict" 错误怎么处理?
这表示你的 DDL 操作与一个正在执行的事务冲突了。常见场景:
- 同时对同一数据库执行多个 ALTER 操作
- 上一个 DDL 还未完成就发起了新的 DDL
解决方法:等待当前事务完成后重试。使用 SHOW TRANSACTIONS 查看进行中的事务。
Q4: MNode Leader 切换后数据会丢失吗?
不会。所有元数据变更都通过 Raft 协议复制到多数派节点后才确认。Leader 切换后,新 Leader 从本地 SDB + WAL 恢复完整状态,并自动继续执行未完成的事务。
Q5: 可以只部署 1 个 MNode 吗?
可以,但不推荐用于生产环境。单 MNode 是单点故障------如果该节点宕机,所有 DDL 操作和元数据查询都不可用(已有数据的读写不受影响,因为客户端有缓存路由信息)。生产环境建议部署 3 个 MNode 副本。
Q6: SDB 的 sdb.dat 文件损坏了怎么办?
如果 sdb.dat 损坏,MNode 启动时会报错。恢复方式:
- 如果有其他 MNode 副本,可以从健康的副本同步(通过 Raft 快照)
- 删除损坏的
sdb.dat,MNode 会通过 WAL 回放重建 SDB(如果 WAL 完整) - 极端情况下,从其他 MNode 副本手动复制数据目录
Q7: MNode 的定时任务会不会影响 DDL 性能?
定时任务(事务拉起、TMQ 重平衡、TTL 清理等)与 DDL 共享写队列,但通常不会造成阻塞,因为:
- 大多数定时任务执行很快(微秒级别的检查)
- 只有需要创建新事务的定时任务(如 TTL 清理)才会占用写队列
- Follower 节点不执行任何定时任务,不影响 Leader
Q8: 如何监控 MNode 的健康状态?
sql
-- 检查 MNode 角色和状态
SHOW MNODES;
-- 检查是否有卡住的事务
SHOW TRANSACTIONS;
-- 检查集群整体状态
SHOW CLUSTER ALIVE;
-- 通过 taosKeeper + Grafana 监控:
-- - MNode Leader 切换次数
-- - 事务执行耗时
-- - SDB 表行数变化
关于 TDengine
TDengine 专为物联网IoT平台、工业大数据平台设计。其中,TDengine TSDB 是一款高性能、分布式的时序数据库(Time Series Database),同时它还带有内建的缓存、流式计算、数据订阅等系统功能;TDengine IDMP 是一款AI原生工业数据管理平台,它通过树状层次结构建立数据目录,对数据进行标准化、情景化,并通过 AI 提供实时分析、可视化、事件管理与报警等功能。