1. 这不是选“哪个更好”,而是选“谁更对路”:一张表讲透 Doris 和 ClickHouse 的本质分野
最近在给一家做实时BI平台的客户做技术选型,他们卡在 Doris 和 ClickHouse 之间反复横跳——前端报表要秒级响应,数据源每天新增2亿行,历史数据要存3年,还要支持多维下钻和用户自定义SQL。我翻了三天文档、搭了四套测试集群、跑了二十多轮压测,最后没写PPT,直接画了一张表贴在会议室白板上:Doris 和 ClickHouse 不是同一类工具,硬要比“谁更快”,就像拿电钻比螺丝刀——都拧螺丝,但电钻干的是批量打孔,螺丝刀干的是精密调校。核心关键词就五个:Doris、ClickHouse、OLAP、SQL、MySQL协议,但每个词背后都藏着截然不同的设计哲学。Doris 官网强调“兼容 MySQL 协议、开箱即用”,ClickHouse 官网首页第一行写着“Designed for analytical workloads”。这句话就是全部答案的起点:一个把“易用性”刻进基因,一个把“极致性能”焊死在内核。你不需要背熟所有参数,但必须搞清三件事:你的查询模式是“固定报表多还是即席分析多”?你的数据更新频率是“分钟级追加还是小时级全量覆盖”?你的团队有没有人愿意为调优写 Python 脚本而不是改 SQL。我见过太多团队因为没想清这三点,上线半年后被迫重做架构——不是数据库不行,是选错了“工作方式”。这张表我后面会逐项拆解,但先说结论:如果你们的 DBA 习惯用 Navicat 连 MySQL 那样连 OLAP 引擎,Doris 是默认选项;如果你们有工程师愿意凌晨三点盯着system.parts表看分区合并状态,ClickHouse 才真正释放价值。
2. 六大核心差异深度拆解:从设计原点到落地陷阱
2.1 存储模型:列存只是起点,底层组织逻辑决定一切
列式存储是 OLAP 数据库的标配,但 Doris 和 ClickHouse 对“列”的理解完全不同。ClickHouse 的存储核心是MergeTree 引擎族,它的最小物理单元叫part(分区片段),每个 part 是一个独立的、不可变的文件夹,里面包含按列压缩的二进制文件(.bin)、索引文件(.idx)、标记文件(.mrk)等。当你执行INSERT INTO table VALUES (...),ClickHouse 不是写入一行,而是攒够一定行数(默认8192行)后,生成一个全新的 part 文件夹。这种设计带来两个硬币的两面:写入时几乎无锁(因为只追加),但读取时要合并多个 part 的结果——这就是为什么 ClickHouse 的SELECT COUNT(*) FROM table可能比 Doris 慢十倍,而SELECT * FROM table WHERE date = '2024-06-01'却快得离谱。它的优化逻辑是:用写放大换读性能,用存储冗余换查询确定性。Doris 的存储模型则基于ColumnStore + Tablet 分片。它把一张表水平切分成多个 Tablet(类似 HBase 的 Region),每个 Tablet 内部再按列存储,但关键区别在于:Doris 的 Tablet 是可变的。当新数据写入,Doris 会触发Compaction(合并),把小的、碎片化的 Tablet 合并成大的、有序的 Tablet。这个过程由后台线程自动完成,用户甚至可以手动触发ALTER TABLE table_name BASE TABLE COMPACTION。这意味着 Doris 的查询性能更稳定——不会因为写入压力导致查询抖动,但代价是写入路径更重,需要协调 Compaction 资源。实操中我遇到过最典型的场景:某电商客户用 ClickHouse 做日志分析,凌晨ETL任务批量导入后,第二天上午报表查询延迟飙升,查system.merges发现正在合并上百个 part;换成 Doris 后,同样的导入节奏,查询 P99 延迟始终控制在200ms内,因为 Compaction 是平滑调度的。所以选型时问自己:你能接受查询性能随写入波动吗?如果业务要求 SLA 稳定,Doris 的 Tablet 模型天然更友好。
2.2 查询引擎:SQL 兼容性背后的语法糖与硬骨头
两者都宣称“支持标准 SQL”,但这个“标准”在 OLAP 场景下水分很大。ClickHouse 的 SQL 引擎叫ClickHouse SQL Parser,它本质上是一个高度定制的 DSL 解析器。它支持绝大多数 ANSI SQL 语法,但对某些关键特性做了妥协。比如窗口函数:ClickHouse 支持ROW_NUMBER(),RANK()等基础函数,但不支持FRAME子句(如ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW),这意味着复杂的时间序列计算必须用arrayJoin或runningDifference等非标函数绕行。再比如JOIN 优化:ClickHouse 默认使用ANY JOIN,即使你写了INNER JOIN,它也可能降级为ANY以提升速度,这会导致结果集去重——如果你的业务依赖 JOIN 结果的精确行数,必须显式指定JOIN USING (col)并确保关联字段唯一。Doris 的 SQL 引擎基于Apache Calcite,这是一个工业级的 SQL 编译框架。它把 SQL 解析成逻辑执行计划(Logical Plan),再通过规则引擎(Rule-based Optimizer)和成本模型(Cost-based Optimizer)生成物理执行计划。这意味着 Doris 对复杂 SQL 的兼容性更高:嵌套子查询、CTE(WITH 子句)、标准窗口函数(包括完整 FRAME 支持)、多表 JOIN 的语义保证都更接近 MySQL/PostgreSQL。我在迁移一个 FlinkSQL 作业到 OLAP 层时深有体会:原 SQL 中有WITH tmp AS (SELECT ..., ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY event_time ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as rn) SELECT * FROM tmp WHERE rn <= 3,ClickHouse 直接报错Unsupported frame specification,而 Doris 一跑就通。但这不是 Doris “更强”,而是设计目标不同:ClickHouse 优先保证单表聚合的极致速度,Doris 优先保证多表关联和复杂分析的语义正确。所以别被“支持 SQL”四个字迷惑,一定要用你的真实业务 SQL 去压测——我建议至少准备三类 SQL:单表聚合(验证基础性能)、多表 JOIN(验证关联能力)、带窗口函数的复杂分析(验证语法兼容性)。
2.3 协议与生态:MySQL 协议不是便利,而是架构约束
这是标题里埋得最深的伏笔。“MySQL 协议”对 Doris 来说不是锦上添花,而是核心架构决策。Doris 的 FE(Frontend)节点内置了一个MySQL 协议兼容层,它能完全模拟 MySQL 5.7 的握手、认证、命令响应流程。这意味着:任何支持 MySQL JDBC 驱动的工具——Navicat、DBeaver、Tableau、甚至 Spring Boot 的spring-boot-starter-jdbc——都能零配置直连 Doris,就像连一个普通 MySQL 库一样。连接字符串jdbc:mysql://doris-fe:9030/test_db?user=root&password=xxx就能工作。这种兼容性带来的好处是巨大的:BI 工程师不用学新客户端,运维不用改监控脚本,应用代码几乎零改造。但代价也很真实:Doris 必须为兼容 MySQL 协议付出额外开销。比如 MySQL 协议要求返回结果集的元数据(column name, type, length),而 Doris 的列存引擎内部类型系统(如DECIMALV3,ARRAY,MAP)需要映射到 MySQL 的有限类型体系(DECIMAL,VARCHAR),这个转换过程在高并发查询下会产生可观的 CPU 开销。ClickHouse 则走另一条路:它原生使用TCP 二进制协议,客户端必须用官方clickhouse-client或特定语言 SDK(如clickhouse-driverfor Python)。虽然社区有第三方 MySQL 协议代理(如clickhouse-mysql-protocol-proxy),但稳定性、功能覆盖(尤其对高级类型)和性能都无法保证。我亲眼见过一个客户用代理连 ClickHouse,结果Array(String)字段在 BI 工具里显示成乱码,折腾两天才发现是协议转换丢失了类型信息。所以选型时问清楚:你的下游工具链是否重度依赖 MySQL 生态?如果是,Doris 的协议兼容性是省下三个月人力的硬优势;如果你们已经用 Flink CDC + Kafka + 自研 SDK 构建了数据管道,ClickHouse 的原生协议反而更轻量、更可控。
2.4 数据更新与实时性:流批一体的两种实现哲学
“实时分析”这个词在 OLAP 场景下常被滥用。Doris 和 ClickHouse 都支持实时写入,但“实时”的定义和实现路径天差地别。ClickHouse 的实时写入本质是Append-Only + 后台 Merge。它没有传统意义上的 UPDATE/DELETE(虽然有ALTER TABLE ... DELETE WHERE,但那是异步的、低效的标记删除),所有写入都是追加。这意味着:如果你需要更新某条订单的状态(比如从“已下单”改为“已发货”),ClickHouse 的做法是插入一条新记录,然后靠ReplacingMergeTree引擎在后台合并时根据version字段保留最新版本。这个过程是非实时的,合并时机由后台线程控制,可能延迟几分钟甚至几小时。Doris 则原生支持Update/Delete 操作,其底层基于Unique Key 模型。当你创建表时指定PROPERTIES("replication_num" = "3", "storage_medium" = "SSD")并设置DUPLICATE KEY (id),Doris 就会为每行数据维护一个主键索引。执行UPDATE table SET status='shipped' WHERE id=12345时,FE 会定位到对应 Tablet,BE 节点直接修改内存中的索引和数据块,整个过程毫秒级完成。这带来了真正的实时性,但也引入了复杂度:Unique Key 模型的写入吞吐量低于 Duplicate Key 模型(因为要维护索引),且对主键字段的选择非常敏感——如果主键区分度低(比如用date作为主键),会导致 Tablet 内部热点。我帮一个金融客户做风控实时看板时,他们要求每笔交易发生后 1 秒内能在看板上看到风险评分变化。用 ClickHouse 方案,我们不得不在 Flink 层做状态管理,把变更事件攒批后写入,最终端到端延迟 3-5 秒;换成 Doris Unique Key 模型,Flink 直接INSERT OVERWRITE,延迟压到 800ms 以内。所以别只看“支持实时”,要问清楚:你的实时是“数据可见实时”还是“业务状态实时”?前者 ClickHouse 足够,后者 Doris 更可靠。
2.5 运维与稳定性:从“开箱即用”到“专家模式”
运维体验是团队技术水位的试金石。Doris 的设计理念是“DBA 友好”。它的 FE 节点负责元数据管理、SQL 解析、查询调度,BE 节点负责数据存储和计算,两者通过 Thrift 协议通信。集群扩缩容极其简单:新加 BE 节点,运行ALTER SYSTEM ADD BACKEND "host:port",Doris 自动均衡 Tablet 分布;下线节点,ALTER SYSTEM DROP BACKEND,自动迁移数据。所有配置都通过 SQL 命令或 Web UI 修改,无需重启服务。它的慢查询日志(slow_log)和 Profile 信息(EXPLAIN ANALYZE)输出格式与 MySQL 高度一致,DBA 看一眼就懂瓶颈在哪。ClickHouse 则奉行“工程师自治”哲学。它的核心配置全在config.xml和users.xml文件里,修改后必须重启服务。扩缩容需要手动操作:新加节点要配置 ZooKeeper(用于分布式 DDL 和副本同步),然后在所有节点的remote_servers配置中添加新节点地址,最后执行CREATE TABLE ... ON CLUSTER。更麻烦的是,ClickHouse 没有内置的负载均衡器,客户端必须自己实现路由逻辑(比如用clickhouse-copier或自研 Proxy)。我经历过最头疼的一次故障:某 ClickHouse 集群因磁盘满导致部分 part 无法写入,system.parts表里出现大量broken状态的 part。修复方法是手动进入数据目录,用clickhouse-client --query="RESTORE PARTITION ..."命令逐个恢复,而 Doris 遇到类似问题,只需ADMIN REPAIR TABLE一条命令。所以评估运维成本时,别只算服务器数量,要算你团队里有几个能熟练操作zookeeper-shell和clickhouse-copier的人。如果团队主力是熟悉 MySQL 的 DBA,Doris 的学习曲线几乎是平的;如果团队有资深 C++ 工程师,ClickHouse 的深度调优空间更大。
2.6 社区与演进:开源项目的“活水”从哪来
技术选型不能只看当下,要看未来三年。Doris(Apache Doris)是 Apache 基金会顶级项目,其核心贡献者主要来自百度、美团、京东等国内大厂,社区活跃度极高。2023 年全年提交 PR 超过 5000 个,Issue 解决率 92%,每月发布一个 Feature Release 版本。它的 Roadmap 清晰聚焦三个方向:云原生适配(K8s Operator)、湖仓一体(Iceberg/Hudi Catalog)、AI 原生(向量检索、JSON 处理)。这意味着如果你计划未来接入 Delta Lake 或部署在 K8s 上,Doris 的演进路径是确定的。ClickHouse 的社区则更“极客化”。它由俄罗斯公司 Yandex 创建并主导,核心开发团队规模较小,但全球贡献者众多。它的演进特点是“性能驱动”:每个大版本(如 23.8, 24.3)必有 2-3 个突破性性能优化(如新的 SIMD 向量化引擎、更低的内存占用)。但对生态兼容性的投入相对保守——比如对 Iceberg 的支持仍处于实验阶段,K8s 部署依赖第三方 Helm Chart。我对比过两个项目的 GitHub Issues:Doris 的 Issue 多是“如何配置 Flink Connector”、“Spring Boot 连接超时怎么调”,属于生产环境问题;ClickHouse 的 Issue 多是“AVX-512 指令在 AMD CPU 上崩溃”、“GROUP BY在 10TB 数据上内存溢出”,属于底层技术攻坚。所以选型时问自己:你的技术栈未来是向云原生、大数据生态演进,还是向极致硬件性能压榨演进?前者 Doris 的路线图更匹配,后者 ClickHouse 的工程师文化更有优势。
3. 实战决策树:六步法精准匹配你的业务场景
3.1 第一步:明确你的核心查询模式(不是“快不快”,而是“怎么快”)
别一上来就跑 TPC-H 基准测试。先梳理你线上最频繁的 5 类 SQL,按执行频次和重要性排序。我给客户做诊断时,会让他们提供一周的慢查询日志样本,然后分类:
| 查询类型 | Doris 优势场景 | ClickHouse 优势场景 | 关键判断指标 |
|---|---|---|---|
单表聚合(如SELECT count(*), sum(amount) FROM sales WHERE dt='2024-06-01') | ✅ 稳定,受写入影响小 | ⚡️ 极致,但可能因 part 数量波动 | 查看SHOW PROC '/frontends'中 FE 节点 QPS 波动率 <5% |
多表 JOIN(如SELECT u.name, o.total FROM users u JOIN orders o ON u.id=o.user_id) | ✅ 语义保证强,支持复杂关联 | ⚠️ 需谨慎,ANY JOIN可能丢行 | 在EXPLAIN结果中确认JOIN类型是否为INNER |
时间范围扫描(如SELECT * FROM logs WHERE ts BETWEEN '2024-06-01' AND '2024-06-07') | ✅ 利用 Partition Pruning 效率高 | ⚡️ 利用Skip Index和MinMaxIndex更快 | 检查system.columns中skip_index是否启用 |
高基数 GROUP BY(如SELECT city, count(*) FROM users GROUP BY city) | ✅ 内存管理更稳,OOM 风险低 | ⚡️ 向量化执行更快,但需调max_bytes_before_external_group_by | 监控system.metrics中MemoryUsage峰值 |
复杂窗口分析(如SELECT user_id, avg(score) OVER (PARTITION BY region ORDER BY ts ROWS BETWEEN 7 PRECEDING AND CURRENT ROW)) | ✅ 完整 FRAME 支持,结果确定 | ❌ 不支持ROWS BETWEEN,需改写 | 尝试执行,看是否报Unsupported frame specification |
提示:如果你们的 TOP3 查询中,有 2 个以上属于“多表 JOIN”或“复杂窗口分析”,Doris 是更安全的选择;如果全是“单表聚合+时间范围扫描”,ClickHouse 的性能红利更实在。
3.2 第二步:评估你的数据更新特征(不是“能不能写”,而是“怎么写才不崩”)
写入模式决定了数据库的“呼吸节奏”。用一张表量化你的数据特征:
| 维度 | Doris 友好场景 | ClickHouse 友好场景 | 验证方法 |
|---|---|---|---|
| 写入频率 | 分钟级增量(如 Flink 每 1min 写一次) | 小时级批量(如 Spark 每 2h 全量覆盖) | 查看 Kafka Topic 的lag和throughput |
| 更新类型 | 频繁 UPDATE/DELETE(如订单状态变更) | Append-Only(如日志、埋点) | 检查业务表是否有status、updated_at字段及高频更新 |
| 数据量级 | 单表日增 1-10 亿行 | 单表日增 10-100 亿行 | 计算(当前数据量 / 天数) * 365得年增长量 |
| Schema 变更 | 频繁 ALTER TABLE(如加列、改类型) | Schema 固定,极少变更 | 统计近 3 个月ALTER TABLE命令次数 |
| 一致性要求 | 强一致性(写入后立即可查) | 最终一致性(允许秒级延迟) | 在写入后 100ms、1s、10s 分别执行SELECT count(*) |
注意:ClickHouse 的
ReplacingMergeTree在高并发 UPDATE 场景下极易产生数据不一致——因为合并是异步的,两个并发写入可能各自生成新 part,最终合并时只保留其中一个版本。Doris 的 Unique Key 模型则通过 Paxos 协议保证强一致性,但写入吞吐会下降 30%-50%。所以别信“ClickHouse 也能 UPDATE”,要看你的 UPDATE 是否允许丢失中间状态。
3.3 第三步:盘点你的技术栈与团队能力(不是“谁更先进”,而是“谁更省心”)
技术选型是团队能力的延伸。用这个清单快速自检:
- 数据库团队:是否有成员熟悉 MySQL InnoDB 的 Buffer Pool、Redo Log 机制?如果有,Doris 的运维模式(
SHOW PROC,ADMIN命令)几乎无缝衔接;如果团队主力是 C++/Rust 工程师,熟悉 Linux 内核、ZooKeeper、Raft,ClickHouse 的深度调优(optimize_table_on_insert,min_bytes_for_wide_part)更能发挥价值。 - 开发团队:是否大量使用 Spring Boot + MyBatis?Doris 的 MySQL 协议让 DAO 层代码零改造;如果用 Flink SQL + Kafka,ClickHouse 的
Kafka Engine表可以直接消费 Topic,减少中间环节。 - BI 团队:是否重度依赖 Tableau/Power BI 的“自动发现字段类型”功能?Doris 的 MySQL 兼容性让 BI 工具能正确识别
DECIMAL、DATE类型;ClickHouse 的Nullable类型在 BI 工具里常被识别为String,需要手动映射。 - 基础设施:是否已上 K8s?Doris 官方提供了成熟的 Helm Chart 和 Operator,一键部署;ClickHouse 的 K8s 部署依赖社区方案,网络策略、存储卷挂载、StatefulSet 配置都需要手工调优。
实操心得:我曾帮一个初创公司选型,他们只有 2 个全栈工程师。我坚持推荐 Doris,理由很现实:他们没时间研究
clickhouse-copier的 YAML 配置,但能用mysql -h doris-fe -P9030 -u root -p五分钟连上并跑通第一个查询。技术选型的第一原则是降低团队认知负荷,不是追求纸面性能。
3.4 第四步:设计最小可行验证(MVP)——用真实数据说话
别被文档说服,用你自己的数据验证。我设计的 MVP 流程如下:
- 数据准备:导出线上 1 天的生产数据(至少 1 亿行),包含你最复杂的表结构(含 JSON、Array 字段)。
- 环境搭建:
- Doris:3 节点(1 FE + 2 BE),配置 16C32G + 1TB SSD,用
docker-compose5 分钟启动。 - ClickHouse:3 节点集群,配置相同,启用
ZooKeeper作为协调服务。
- Doris:3 节点(1 FE + 2 BE),配置 16C32G + 1TB SSD,用
- 加载测试:
- 执行
INSERT SELECT导入数据,记录耗时、CPU/IO 使用率。 - 模拟业务写入:用
sysbench或自写脚本,每秒 1000 条 INSERT,持续 1 小时,观察查询延迟变化。
- 执行
- 查询压测:用你真实的 TOP5 SQL,分别在 1、10、100 并发下执行,记录 P50/P95/P99 延迟、错误率。
- 故障注入:随机 kill 一个 BE 节点(Doris)或一个 ClickHouse 实例,观察查询是否中断、数据是否丢失、恢复时间。
关键指标红线:Doris 的 P95 延迟在 100 并发下应 ≤ 500ms;ClickHouse 的单表聚合 P95 应 ≤ 200ms。如果任一方案在 MVP 中出现数据丢失、查询超时率 > 1%,直接淘汰。
3.5 第五步:成本核算——不只是服务器钱,还有隐性成本
总拥有成本(TCO)常被忽略。我帮客户算过一笔账:
| 成本项 | Doris | ClickHouse | 说明 |
|---|---|---|---|
| 硬件成本 | 相当 | 相当 | 同等配置下性能接近,无显著差异 |
| 人力成本(首年) | 1 人月(部署+调优) | 3-6 人月(部署+ZK调优+Proxy开发+故障处理) | ClickHouse 的zookeeper配置、distributed表路由、materialized view刷新逻辑都需要深度定制 |
| 工具链改造成本 | 几乎为 0(JDBC 驱动兼容) | 1-2 人月(BI 工具适配、监控脚本重写、告警规则重构) | ClickHouse 的system.metrics表结构与 Prometheus 监控体系需重新对接 |
| 学习成本 | DBA 1 周上手 | 工程师 1 月精通 | Doris 文档中文完善,ClickHouse 文档英文为主,且概念抽象(如prewhere,index_granularity) |
| 升级风险 | 无缝滚动升级 | 需停机或灰度,ZooKeeper 版本兼容性复杂 | Doris 的 FE/BE 分离架构支持在线升级,ClickHouse 的ALTER TABLE在大表上可能阻塞数小时 |
真实体验:某客户最终选择 ClickHouse,但上线后花了 2 个月时间让 DBA 学会看
system.processes和system.query_log,又花了 1 个月开发了一个自动清理brokenpart 的脚本。这些成本在选型时都没算进去。
3.6 第六步:长期演进评估——你的架构能否跟上业务增长
画一张 3 年技术路线图:
- Doris 路径:当前用 MySQL 协议接入 BI → 1 年后接入 Iceberg Catalog,统一湖仓元数据 → 2 年后在 K8s 上用 Operator 管理,实现弹性伸缩 → 3 年后集成向量引擎,支持 AI 特征检索。
- ClickHouse 路径:当前用
Kafka Engine实时摄入 → 1 年后用MaterializedView做预聚合,降低查询压力 → 2 年后引入ClickHouse Keeper替代 ZooKeeper,简化运维 → 3 年后用EmbeddedRocksDB存储小表,解决 Join 性能瓶颈。
关键洞察:Doris 的演进是“生态整合”,目标是成为企业数据平台的统一查询层;ClickHouse 的演进是“性能纵深”,目标是把单机性能压榨到极致。如果你的业务未来会接入更多数据源(Hive, Iceberg, Delta),Doris 的 Catalog 抽象层让你少写 80% 的元数据同步代码;如果你的业务未来会挑战 PB 级单表分析,ClickHouse 的 SIMD 向量化引擎可能是唯一解。
4. 常见问题与避坑指南:那些文档里不会写的实战真相
4.1 Doris 常见问题速查表
| 问题现象 | 根本原因 | 解决方案 | 我的实操备注 |
|---|---|---|---|
查询超时,show proc '/current_queries'显示state=FINISHED但客户端收不到结果 | FE 节点内存不足,GC 导致响应阻塞 | 调大-Xmx(建议 ≥ 16G),开启-XX:+UseG1GC | 我们把 FE 的-Xmx从 8G 调到 24G,超时率从 12% 降到 0.3% |
INSERT INTO SELECT导入慢,BE 节点 CPU 持续 100% | 数据倾斜,某个 Tablet 接收了 90% 的数据 | 用SHOW PROC '/backends'查看tablet_num分布,手动ALTER TABLE ... ROLLUP重建 Rollup | 避免用date作为分桶字段,改用city_id或user_id % 100 |
Spring Boot 连接 Doris 报Could not add role column to users table | JDBC 驱动版本过低,不支持 Doris 2.x 的权限模型 | 升级mysql-connector-java到 8.0.33+,或改用doris-stream-load-sdk | 官方 JDBC 驱动对 Doris 的ROLE语法支持滞后,用 SDK 更稳 |
ALTER TABLE ... COMPACTION手动触发后,查询延迟不降反升 | Compaction 过程消耗大量 IO,抢占查询资源 | 在业务低峰期执行,或设置compaction_mem_limit限制内存使用 | 我们设SET GLOBAL compaction_mem_limit = 2147483648;(2GB),避免 OOM |
EXPLAIN ANALYZE显示ScanNode扫描行数远大于实际,但FilterNode过滤率很低 | WHERE条件未命中Skip Index,或Bitmap Index未生效 | 检查SHOW CREATE TABLE中skiplist和bitmap索引是否启用,对高基数字段慎用Bitmap | Bitmap Index对user_id(10亿唯一值)无效,对status(3个值)效果显著 |
4.2 ClickHouse 常见问题速查表
| 问题现象 | 根本原因 | 解决方案 | 我的实操备注 |
|---|---|---|---|
SELECT * FROM table返回空结果,但SELECT count(*)有数据 | ReplicatedMergeTree表的副本未同步完成,system.replicas显示is_leader=0 | 执行SYSTEM SYNC REPLICA,或检查 ZooKeeper 连接 | 我们在users.xml中增加<zookeeper><node><host>zk1</host><port>2181</port></node></zookeeper> |
INSERT写入后,SELECT立即查不到新数据 | insert_quorum设置为 2,但只有 1 个副本写入成功 | 检查system.replicas的queue_size,调低insert_quorum或确保 ZooKeeper 稳定 | 生产环境建议insert_quorum=1,用OPTIMIZE TABLE ... FINAL保证最终一致性 |
GROUP BY查询内存溢出,报Memory limit (for query) exceeded | max_bytes_before_external_group_by设置过小,或GROUP BY字段基数过高 | 调大该参数(如10737418240),或改用GROUP BY ... WITH ROLLUP | 我们把max_bytes_before_external_group_by设为21474836480(20GB),并监控system.metrics中QueryMemoryLimitExceeded |
ALTER TABLE ... DELETE WHERE执行后,SELECT仍能看到旧数据 | ReplacingMergeTree的version字段未更新,或FINAL关键字未加 | 查询时加FINAL,或确保DELETE语句中version字段正确递增 | FINAL会强制触发合并,性能损耗大,建议在低峰期用OPTIMIZE TABLE ... FINAL |
clickhouse-client连接超时,报Code: 210. DB::NetException | tcp_keepalive_timeout过短,或防火墙中断长连接 | 在config.xml中设置<tcp_keepalive_timeout>3600</tcp_keepalive_timeout> | 我们还增加了max_concurrent_queries=100,避免连接池耗尽 |
4.3 那些血泪教训:踩过的坑比文档还重要
- ClickHouse 的
part命名不是随便的:它的命名规则是YYYYMMDD_XXXX_YYYY_ZZZ(XXXX是 min block number,YYYY是 max block number,ZZZ是 level)。如果你用clickhouse-backup工具恢复数据,必须确保备份时的block_number与当前集群一致,否则恢复后system.parts里会出现broken状态。我吃过亏:一次误操作导致block_number错位,花了 8 小时手动DETACH PART+ATTACH PART修复。 - Doris 的
variant类型在 Java 中要小心:doris variant java不是标准 JDBC 类型,Spring Boot 的JdbcTemplate会把它当成Object,反序列化失败。解决方案是:用ResultSet.getObject()获取String,再用 Jackson 解析;或者在建表时避免variant,改用JSON类型(Doris 2.0+ 支持)。 - 别迷信
SQL Server 2008 R2 下载这类搜索词:很多客户拿 SQL Server 的经验套 OLAP,比如以为SQL Server 时间函数(GETDATE())在 Doris/ClickHouse 里也一样。实际上 Doris 用now(),ClickHouse 用now64(),且时区处理逻辑完全不同。我建议:把所有业务 SQL 中的函数列个表,逐一验证替换。 flinksql 写入 doris union key 模型的表有隐藏陷阱:Flink 的DorisSink默认用StreamLoad,但UNIQUE KEY模型要求StreamLoad的columns参数必须包含主键字段,否则写入会失败。必须在sink.properties.columns中显式指定id,name,status,而不仅仅是name,status。windows 上部署 doris是伪需求:Doris 官方不支持 Windows,所有 Windows 部署都是 WSL2 或 Docker Desktop。我见过客户在 Windows 上硬装,结果BE节点因epoll不可用而崩溃。正确姿势:用docker run -d --name doris -p 9030:9030 apache/doris:2.0.0。
5. 最后一点个人体会:技术没有银弹,只有适配的智慧
写完这张表,我关掉所有文档,打开终端连上我们刚上线的 Doris 集群,执行了一条最简单的SELECT count(*) FROM events。0.12 秒,结果返回。这不是什么惊人的数字,但背后是 3 个月的选型、2 周的压测、1 天的故障演练。技术选型从来不是