适用范围与共同基线
本文用于 Doris 3.x、StarRocks 3.3+ 与 ClickHouse 之间的同口径 OLAP 对账,适合迁移、双读和物化视图验收。所有操作先核对实际版本、表模型与设置;示例基于隔离的规范化样本,不假定三个引擎处于同一个事务快照。本文不执行 SQL,验证等级保持 PENDING。
对账开始前写清指标:统计事件还是订单当前状态,金额是分值增量还是修订总额,按事件时维度还是当前维度归类。再固定源快照、截止位点、时间窗口、币种和去重规则。两个查询都叫“交易额”,并不证明它们回答同一个业务问题。
先证明读取的是同一批输入
分别记录源端范围与各引擎确认可见的进度,不能用查询开始时间相近冒充共同快照。Doris 事务已提交但未可见、StarRocks 视图尚未刷新、ClickHouse 历史回填未完成,都可能产生时间性差异。
用于验证的封闭窗口应包含一份确定的输入清单,并确保后续修订已冻结或被排除。事件数、去重后业务键数、删除数和有效状态数分别统计。若一侧保存事件而另一侧按主键覆盖,先还原到共同粒度,再比较指标,模型背景见 Doris 表设计。
时区、时间精度与日期边界
保存原始时间字符串或 epoch、单位、来源时区和目标字段精度。毫秒当秒会改变数量级;把不带时区的墙钟时间当 UTC,也会把边界事件分到错误日期。日报应先定义业务时区,再转换为明确的左闭右开 UTC 区间。
Doris 与 StarRocks 可用 CONVERT_TZ 进行明确转换,但不能假设更改会话时区会自动修复已入库的 DATE/DATETIME 值。ClickHouse DateTime64 的精度与时区属性也要单独记录。依据见 Doris 时区、StarRocks 时区 和 DateTime64。
边界样本至少包含区间起点、终点前的最小精度单位、终点以及夏令时切换附近事件。不要假定所有本地自然日都等于 24 小时,也不要在双方使用不同精度的 BETWEEN 上下界。时区转换规则和数据库时区数据版本应随对账记录保存。
NULL、空串和未匹配维度
COUNT(*) 统计行数,COUNT(amount) 排除 amount 为 NULL 的行,SUM 对空集合或全 NULL 的返回还需按具体查询类型核验。先单列 NULL 数量,再决定是否把缺失值转为零;默认填零会掩盖导入映射失败。
ClickHouse 外连接未匹配值受 join_use_nulls 影响,可能是 NULL,也可能是数据类型默认值。若一侧用 NULL 判断未匹配,另一侧得到 0 或空串,分类指标会出现差异。应核对会话设置并使用可辨识的匹配标记,参见 ClickHouse Join 建议。不能未经证明就认定合法维度键 0 等于“未知”。
金额、除法与聚合精度
优先使用明确币种的整数最小单位或固定精度 Decimal 保存金额,并记录乘除发生在聚合前还是聚合后、舍入位数与方式。逐行四舍五入再 SUM 与先 SUM 再舍入可能不同,不能随意放大误差容忍度来消除差异。
固定精度也有溢出与缩放边界。ClickHouse Decimal 除法的缩放和精度规则不能直接按浮点结果推断,整数与 Decimal 混合表达式亦需核验,见 Decimal 运算。平均值应保留分子与分母,避免再次平均各分区平均值;精确去重也不应与 HLL、bitmap 或近似函数的结果未经约定直接比较。
在隔离快照上建立小范围指纹
假设三个引擎各自已有经过核对的 lab.recon_snapshot,列含相同 snapshot_id、UTC 日期、租户、稳定 bucket_id、事件键及可空整数分值。每个桶的输入规模已登记,示例只检查一个桶:
SELECT COUNT(*) AS rows_all,
COUNT(amount_minor) AS rows_with_amount,
SUM(CASE WHEN amount_minor IS NULL THEN 1 ELSE 0 END) AS rows_null,
SUM(CAST(amount_minor AS DECIMAL(38, 0))) AS total_minor
FROM lab.recon_snapshot
WHERE snapshot_id = 'recon_20260901_v1'
AND event_day_utc = '2026-09-01'
AND tenant_id = 42 AND bucket_id = 7;输入金额和汇总值必须先证明落在类型范围内。若约定样本为 100 行、2 行 NULL,则 rows_with_amount 应为 98、rows_null 为 2;总额需与独立来源核对。这里列名和类型是前置契约,不是让同一 SQL 直接作用于三套不同生产表。
从指标差异下钻到业务键
先按日期和租户对比,再对异常桶比较事件键集合,最后查看同键的版本、金额、删除和维度。总数相同可能存在一漏一重,总额相同也可能存在正负抵消。应分类记录缺失键、多余键和相同键字段不一致,避免直接把所有差异归为延迟。
哈希仅能辅助定位,不能替代原始键检查。跨引擎直接对比默认 hash 函数值没有意义,算法、字符编码、NULL 标记、Decimal 序列化和字段顺序都可能不同。若需要指纹,应由共同规范定义字节表示和算法,并保留少量可回溯明细。
维表更新与物化时间
对账需要约定维度采用事件发生时版本还是查询时最新版本。ClickHouse 增量视图中的右表更新不会追溯触发已生成结果;Doris、StarRocks 异步视图则需要观察刷新覆盖与时间。不能用“源表最终相同”推断物化结果马上相同,机制见 ClickHouse 增量视图。
准备一个商户分类从 A 改为 B 的样本,同时保存变化前后事件与维表版本。若双方总金额相同但 A、B 分类相反,应先核对物化与关联时间,而不是立即重导事实表。基表基线还要确认没有透明重写到同一个待验视图。
验收、停止与恢复
通过条件是约定输入窗口、业务键集合、NULL 数量、金额、分类和数据截止点都能解释;容许误差仅用于事先约定的近似计算或浮点指标,不能掩盖确定性的缺失。每条差异都应带来源快照、SQL、设置、查询 ID 与归因依据。
若窗口仍变化、源端位点不明、数据类型溢出或基线不独立,停止发布“已对平”结论。保留两侧原始结果和旧读路径,优先修正转换或口径后重新建立样本。对账本身不修复数据;补写、删除和重新回填应使用独立方案,避免在差异原因未知时破坏证据。相关实践见 Doris 视图刷新、StarRocks 新鲜度 和 ClickHouse 回填。