《通过SeaTunnel 把 MySQL 的数据搬进 Doris》番外篇:容量、切分与速度——Doris 能扛多少,比 MySQL 快多少

《通过SeaTunnel 把 MySQL 的数据搬进 Doris》小白学习系列

SeaTunnel + Doris + MySQL-CDC 数据同步故障排查个人笔记

前面几篇把"怎么同步、故障排查"讲完了,这篇补的是"同步过来之后"的问题:表会不会爆?Doris 到底比 MySQL 快多少?数据在 Doris 里是怎么切分存放的?
先说结论:MySQL 的瓶颈是"数据量" ------单表行数到了一个量级,性能就滑坡,这是结构决定的;Doris 的瓶颈是"元数据规模"------单表行数本身几乎不是问题,真正卡住它的是表切得太碎、分区太多,把 FE(管理节点)的内存撑爆了。一个被数据量限制,一个被"管理开销"限制。记住这句话,下面的表格就都好懂了。


一、MySQL 的瓶颈在哪

MySQL 单表的"2000 万行"你应该听过。先澄清:这不是 MySQL 代码里写死的限制,是行业经验值。理论上 InnoDB 单表能存到 TB 级,但过了某个量级,体验会断崖式下降。滑坡的原因有四个:

  1. B+ 树变深。MySQL 靠 B+ 树索引查数据,2000 万行左右正好是索引从 3 层变 4 层的临界点,每次查询多一次磁盘 IO,点查延迟肉眼可见地变慢。

  2. 内存装不下热数据。buffer pool(缓冲池)就那么大,表大了命中率下降,查询开始频繁读磁盘。

  3. DDL 变成灾难。给一张 5000 万行的表加个字段,锁表几十分钟,这期间业务可能直接不可用。

  4. 备份、恢复、碎片整理全都变慢,运维窗口越来越难挤。

MySQL 单表容量参考表

单表行数 状态 你会观察到什么
500 万以内 舒适区 读写都快,索引高效,基本不用操心
500 万 ~ 1000 万 正常 还扛得住,开始留意慢查询
1000 万 ~ 2000 万 预警区 报表类查询明显变慢,该做容量评审了
2000 万以上 高危区 慢查询频发、加字段锁表、备份超时,考虑分表/分区/归档
5000 万以上(分析型查询) 基本没法用 一条 GROUP BY 跑几分钟很正常

再次强调:这个表说的是"分析型查询"的体验 。如果是纯点查(按主键查一行),MySQL 到几千万行依然很快------它就是为这个生的。它扛不住的是"扫一大片、算一堆"。


二、Doris 的瓶颈在哪

先看几个真实生产案例,感受一下它的量级:

案例 规模
浩瀚深度 单表 13PB、534 万亿行,日均导入 145TB,117 节点集群
小米 单表日增 120 亿行,PB 级存储,单集群日均 2 万+ 分析查询
美团 数十 PB 级统一 OLAP,支持百亿级数据量的单 SQL 查询

所以"一张表能存多少行"这个问题,对 Doris 来说基本没有有意义的答案------你大概率先用不到它的行数上限。

那它到底会被什么卡住?

瓶颈点 说明 症状
Tablet 数量(真正的上限) Doris 把表切成 tablet 分布到各节点,单集群 tablet 总数约 100 万个是经验上限,受 FE 内存制约 FE 内存持续飙高、元数据同步变慢、FE 冷启动很久
分区切太碎 按天分区 + 大量表 + 大量分桶,tablet 数会爆炸式增长(这笔账第三章算给你看) 同上
FE 单点元数据压力 所有表的元数据都装在 FE 的 JVM 内存里,tablet 越多压力越大 集群规模大了之后 FE 成为短板
写入吞吐 单节点 Stream Load 约百万行/秒,一般场景够用 除非日导入量上百 TB
并发查询 适合低中并发大查询;高并发点查不是它的主场 把它当业务库高并发查会难受

给小白的实操建议 :Doris 建表时把"分桶数"想清楚。分桶太少,数据倾斜、并行不起来;分桶太多,tablet 数暴涨,伤 FE。经验值是单个 tablet 1~10GB 数据量。


三、分区与分桶:数据在表里是怎么"切"的

回答一个建表时躲不开的问题:PARTITION BY 和 DISTRIBUTED BY 到底啥区别?先把一句话放前面:

分桶是强制的,分区才是可选的。 Doris 里每张表都必须分桶;分区爱建不建。

1. 一句话定义

想象一个图书馆:

  • 分区 :按"楼层"分------1 楼放 2023 年的书,2 楼放 2024 年的书,3 楼放 2025 年的书。这是按规则把数据分堆。

  • 分桶 :每层楼里再按"书架号"分------把这层的书按书名哈希扔到 10 个书架上。这是把每堆数据切片,散到多台机器上并行干活。

先分堆(分区),堆内切片(分桶)。

2. 逐项对比

对比项 分区(Partition) 分桶(Bucket)
是什么 按规则把数据分成大堆 把每堆数据切成小片(tablet)
用什么切 时间范围、枚举值等(RANGE / LIST) 对某列取哈希(HASH)或随机(RANDOM)
是否必须 可选 必须,不写 Doris 也给默认分
列的限制 分区列必须是 Key 列 任意列都行(一般选主键)
建好后能改吗 能加、能删分区 桶数建表定死,不能改
解决什么问题 查询少扫数据、按时间删数据、生命周期管理 并行计算、数据均匀散到多台 BE
类比 楼层 / 文件夹 书架 / 文件分片

3. 拿行为日志表画两张图

以 ihm_user_behavior_log(用户行为日志)为例,只分桶不分区是这样的:

TypeScript 复制代码
ihm_user_behavior_log
└── 默认分区(一整个,所有年份的日志全在这)
    ├── 桶0  id%10=0 的所有行  ──→ BE 节点 A
    ├── 桶1  id%10=1 的所有行  ──→ BE 节点 B
    ├── ...
    └── 桶9  id%10=9 的所有行  ──→ BE 节点 C
加了按天分区之后:

ihm_user_behavior_log
├── p20261010(10月10日的日志)
│   ├── 桶0 ──→ BE A      每个分区内部各自分桶
│   ├── 桶1 ──→ BE B
│   └── ...桶9
├── p20261011(10月11日)
│   ├── 桶0 ──→ BE A
│   └── ...
└── ...

注意:分桶在每个分区内部独立进行。分区从 1 个变 365 个,桶从 10 个变 3650 个。这笔账就是第二章说的"tablet 数 = 分区数 × 桶数",tablet 切得太碎会撑爆 FE,所以分区和分桶要联动设计,不能俩都往大了开。

4. 查询的时候,俩各干各的活

sql 复制代码
SELECT app_name, COUNT(*)
FROM ihm_user_behavior_log
WHERE create_time >= '2026-10-01'
GROUP BY app_name;

分区先干活(做减法):一看条件是 10 月,直接把之前的分区全部扔掉不看。存了两年数据的话,700 多个分区只扫十几个------这叫"分区裁剪",是分区最大的价值。

分桶再干活(做加法) :剩下分区里的 10 个桶同时开算,10 路并行,最后汇总。BE 节点越多,并行越给力。

一句话总结分工:

分区让查询"少看",分桶让查询"快算"。

对写入和清理也一样:删一年前的日志,无分区表要 DELETE 几亿行(慢且产生垃圾),分区表 DROP PARTITION 一秒完事。

5. 建表规则与实战改造

三个硬规则先记住:

  1. 分区列必须是 Key 列 。比如 UNIQUE KEY(id) 的表想按 create_time 分区,会直接报错------解法是把分区列挪进 Key:UNIQUE KEY(id, create_time)(id 唯一,联合键自然唯一,不影响 CDC 同步)。

  2. Range 分区列只支持 DATE、DATETIME、整数。拿 varchar 存的时间戳当分区列,没戏。

  3. 无分区表不能事后变成分区表。分区方式建表定终身,只能重建表搬数据。

行为日志表的分区改造 DDL(动态分区版):

sql 复制代码
CREATE TABLE `ihm_user_behavior_log` (
  `id`          bigint   NOT NULL COMMENT "主键",
  `create_time` datetime NOT NULL COMMENT "创建时间(分区列)",
  -- ... 其余字段同 MySQL 侧 ...
) ENGINE=OLAP
UNIQUE KEY(`id`, `create_time`)              -- ① 分区列进 Key
PARTITION BY RANGE(`create_time`) ()         -- ② 按创建时间切
DISTRIBUTED BY HASH(`id`) BUCKETS 5          -- ③ 每个分区内再分 5 桶
PROPERTIES (
  "replication_allocation" = "tag.location.default: 1",
  "dynamic_partition.enable"   = "true",     -- ④ 动态分区管家
  "dynamic_partition.time_unit" = "DAY",
  "dynamic_partition.start"  = "-730",       --    保留最近 730 天,过期自动删
  "dynamic_partition.end"    = "3",          --    预建未来 3 天
  "dynamic_partition.prefix" = "p",
  "dynamic_partition.buckets" = "5",
  "dynamic_partition.replication_num" = "1",
  "enable_unique_key_merge_on_write" = "true"
);

动态分区就是"分区管家":自动建明天的分区、自动删过期的分区,你永远不用管"分区够不够用""陈年数据怎么清"。

三个坑提前打招呼:

坑 症状 解法
"no partition for this tuple" 写入的数据落在分区范围外,全量同步最常撞上 start 一定要覆盖到 MySQL 里最老的那条数据,宁可写 -3650
分区数上限 每张表默认最多 4096 个分区,按天 ≈ 11 年,按小时 ≈ 170 天 撞了调 FE 配置 max_dynamic_partition_num
事后想加分区 不支持 建新表重灌。CDC 同步最省事的路径:drop 旧表 → 新 DDL → 重启作业,全量+增量自动重来

6. x张表怎么定分区策略

表类型 例子 策略
日志流水表,只增不删涨得快 行为日志、操作日志 必须分区,按天 + 动态分区
业务明细表,千万~亿级 就诊记录、费用明细 建议分区 ,量不大就按月(time_unit = "MONTH")
维度表、配置表,几万行 科室字典、渠道表 不分区,桶分 3 个就完
会被 UPDATE 的状态表 订单状态、随访进度 量不大不分区;更新多的行跨分区有额外开销

判断法:预计单表最终超过 5000 万行,就上分区。


四、Doris 比 MySQL 快多少(实测对比)

先说结论的量级:点查 MySQL 更快,分析查询 Doris 快 5~10 倍,复杂多表 JOIN 能快 8 倍以上,差距随数据量放大。

同机部署的实测数据(Doris 1.2 vs MySQL 8.0,亿级数据场景):

查询类型 MySQL 耗时 Doris 耗时 Doris 快多少 谁赢
主键点查一条记录 2.1ms 3.5ms 慢 ~60% MySQL
二级索引查一条记录 8.7ms 12.3ms 慢 ~40% MySQL
全表 COUNT 4.2s 0.8s 快 ~5 倍 Doris
日期范围聚合(GROUP BY) 6.8s 1.2s 快 ~5~6 倍 Doris
5 表 JOIN + 聚合 28.5s 3.4s 快 ~8 倍 Doris

多种 OLAP 场景的对比测试结论一致:

场景 相对速度
简单单表聚合 差距不大(1~2 倍)
多表 JOIN Doris 快 3~5 倍
复杂聚合计算 Doris 耗时约为 MySQL 的 1/4
时间序列分析 列存优势明显,5 倍以上
内存占用 相同查询下 Doris 比 MySQL 省 ~30%

还有个更直观的说法:同样的 SQL,从 MySQL 挪到 Doris,耗时大约降到原来的十分之一。

但必须把话说全:数据量越大,差距越大

数据量 MySQL 聚合查询 Doris 聚合查询 差距
百万级 几百毫秒 几十~几百毫秒 不明显,别为了这个上 Doris
千万级 几秒 ~ 十几秒 1 秒内 5~10 倍
亿级 十几秒 ~ 几分钟 1~3 秒 10 倍以上
十亿级 跑不动/锁死业务 秒级 ~ 十几秒 差距拉到没法比

百万级数据量上 Doris 是没必要的,MySQL 完全扛得住。Doris 的价值从千万级开始兑现------医疗业务库里就诊、费用这类表大概率是千万到亿级,正好落在收益区间里。

顺带说清楚"Doris 为什么快"的三个来源,跟第三章正好呼应:

  1. 列存------只读查询涉及的列,IO 砍掉九成;

  2. 分区裁剪------按时间查只扫相关分区(所以行为日志表要分区);

  3. 分桶并行------一条 SQL 拆到多台 BE 同时算(所以分桶不能只分 1 个)。


五、一页总结

维度 MySQL Doris
单表行数上限 经验值 ~2000 万,不是硬限制 实际无行数上限,生产案例 534 万亿行
真正的瓶颈 B+ 树深度、buffer pool、DDL 锁表 Tablet 数量(~100 万)撑爆 FE 内存
数据切分 分库分表(应用层自己折腾) 分区(可选,管分堆)+ 分桶(必须,管切片并行)
切分能改吗 分完后悔只能再迁 分区可增删;桶数定死
点查一条记录 2.1ms,赢 3.5ms
单表聚合 秒~分钟级 快 5~6 倍
多表 JOIN 十几秒~分钟级 快 5~8 倍
亿级数据分析 基本不可用 秒级
写入方式 事务写,高并发点写 Stream Load 批量灌,单节点百万行/秒
角色定位 业务读写 分析查询

给 x 张表的行动清单:

  1. 每张表先估"最终多大",超 5000 万行的上分区;

  2. 日志流水表按天分区 + 动态分区,start 覆盖全量数据的最早时间;

  3. 分区列选 datetime 类型的列(如 create_time),并把它挪进 Key;

  4. 分桶列用主键,桶数按"单桶 1~10GB"倒推,宁少勿多(tablet 数 = 分区数 × 桶数);

  5. 维度小表别凑热闹,不分区,少分桶。

两句话带回去够用很久:分区管分堆,分桶管并行;分区列必须进 Key,桶数建表定终身。

相关推荐
打工仔折腾 AI2 小时前
260+异构同步链路实战:如何用语义化自动翻译破解CDC DDL同步痛点
人工智能·后端·python·自然语言处理·性能优化·langchain·机器翻译
曦夜日长2 小时前
MySQL系统篇,数据库基础(二):数据库概念、SQL 分类与存储引擎
数据库·sql·mysql
要开心吖ZSH2 小时前
SeaTunnel + Doris + MySQL-CDC 数据同步故障排查个人笔记
数据库·mysql·doris·seatunnel·mysql cdc
风若寒893 小时前
MySQL 主从同步,从库中断时间过长,需要主库全量进行覆盖一次
数据库·mysql·主从
Sayai3 小时前
Java 进程 Unable to create new native thread 排查实战:Linux 线程数五层限制与 JVM -Xss 调优
java·linux·运维·jvm·性能优化
知行产研4 小时前
三年三起收购,拆解卡特彼勒AI并购版图
数据库·人工智能·mysql
打工仔折腾 AI4 小时前
让时序预测真正参与决策:TimechoAI 工程化落地与管线设计实践
人工智能·后端·python·算法·机器学习·性能优化
可乐ea5 小时前
微调提速 6 倍:Qwen 27B 从 7 小时 44 分到 1 小时 17 分
性能优化·模型微调·训练加速·fsdp2·数据打包
2301_32241428045 小时前
springboot学生课堂出勤统计系统15835-计算机毕设 原创(免费领源码+部署教程+开发环境)
java·mysql·随机森林·微信小程序·uni-app·微信开发者工具·推荐算法