Unique模型在Doris写时合并失败?TaoToken这样让Codex查 Apache Doris 的 UNIQUE KEY 表导完两批数据同一主键查出来还是旧值这种「写时合并像没生效」的情况先用 TaoToken 把排查通道搭起来最省事打开 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_end 注册并创建一把 API Key再把 Codex 的 Base URL 填成 https://taotoken.net/api让它在对话里对照 Doris 对 Unique 模型写时合并merge on write的说明帮你把建表语句、导入批次和查询版本逐项对一遍。TaoToken 在这里只做一件事——把这把 Key 和模型通道连通它不参与 Doris 的合并计算也不会替你去连你的库。真正要查的是 Doris 自己的行为。原文在讲 Unique 模型时给了一个很关键的边界同一个导入批次内的数据对 REPLACE 这种聚合方式替换顺序不做保证不同导入批次之间才可以保证后一批次替换前一批次。很多人第一次建 Unique 表看到「旧值没被替换」就以为是写时合并失效其实问题常常出在批次边界、建表语句里的 Key 列、以及 compaction 还没追上。下面按原文的模型脉络把这套排查写成可落地的步骤。1. UNIQUE KEY 表旧值没替换先分清批次内和批次间1.1 原文那句关于 REPLACE 的关键说明原文用 Aggregate 模型举了一个用户访问事实表的例子同一个用户同一天有两行数据last_visit_date是 REPLACE 聚合导入后只保留一行值可能是 06:00:00 也可能是 07:00:00因为同批次内 REPLACE 的替换顺序不保证。后面又补了一句不同批次之间后一批次才会替换前一批次。这两句话几乎能解释一大半「Unique 表旧值还在」的疑问。Unique 模型在 1.2 之前本质上就是 Aggregate 模型的一个特例把所有 Value 列都设成 REPLACE用UNIQUE KEY简写出来。所以在读时合并merge on read的时代你查 Unique 表Doris 是查询时才把多版本按 REPLACE 规则揉成一行到了 1.2 引入写时合并merge on write合并动作被挪到写入侧查询直接读合并后的结果。如果你的两行新数据落在同一个导入批次里主键相同那替换顺序本身就不保证——先导入的那条旧值留到最后是完全符合语义的不是故障。1.2 写时合并 vs 读时合并先确认你查的是哪条路径写时合并和读时合并不是同一个东西排查前先确认表实际走的是哪条读时合并数据按多版本存查询时按主键合并UNIQUE KEY表默认可能还是这一套写时合并写入阶段就把同主键的旧版本标记、compaction 时物理整理查询拿到的已经是合并结果1.2 之后两者短暂共存具体走哪条受建表属性、版本和 compaction 状态影响。所以「旧值没被替换」有三种常见可能新数据其实没成功进同一个副本新数据和旧数据在同一批次内、顺序不利写时合并属性没生效查询侧仍在读旧版本行。这三件事都能用 Codex 生成 SQL、你在本地或 SQL 客户端执行来验证而不是让 AI 去连你的库。2. 把 Codex 的 Base URL 指到 TaoToken 通道2.1 在官网拿 Key并确认模型广场里的模型 ID先打开 TaoToken 注册进控制台创建一把 API Key记下占位符替换用的真实值顺手在模型广场看一眼当前可用的模型 ID后面填配置时以模型广场当时列表为准不要随手编一个带日期后缀的名字。这一步只解决两件事给 Codex 一把能用的 Key以及给它一个正确的模型 ID。Doris 的合并逻辑、compaction、导入批次都由你自己的 Doris 集群决定通道不参与其中。2.2 ~/.codex/config.toml 里改 model_provider 和 base_urlCodex 读的是~/.codex/config.toml别把 Claude Code 的ANTHROPIC_*环境变量套过来。自定义供应商按下面这样写model YOUR_MODEL_ID model_provider taotoken [model_providers.taotoken] name TaoToken base_url https://taotoken.net/api env_key TAOTOKEN_API_KEY其中base_url结尾不要加/v1Key 通过环境变量TAOTOKEN_API_KEY传入值就是你在官网创建的那把。保存后新开一个终端会话让环境变量生效再启动 Codex。确认通道连通之后后面的对话都围绕 Doris 的建表和导入展开Codex 只负责解释和生成 SQL不会自动碰你的数据库。3. 让 Codex 生成 Unique 模型检查清单SQL 拿到本地跑3.1 先核对 UNIQUE KEY 的 Key 列和建表属性原文的 Unique 例子主键是user_id usernameValue 列全部是 REPLACE 语义。你把建表语句贴进 Codex让它按 Unique 模型的规则逐条对照Key 列是否真的唯一约束、有没有把本该 REPLACE 的列漏在 Key 外面、分桶键是否合理、enable_unique_key_merge_on_write属性有没有打开。一个可运行的 Unique 建表骨架可以写成这样CREATE TABLE IF NOT EXISTS example_db.user_profile ( user_id LARGEINT NOT NULL COMMENT 用户 id, username VARCHAR(50) NOT NULL COMMENT 用户昵称, city VARCHAR(20) COMMENT 所在城市, phone LARGEINT COMMENT 联系电话, address VARCHAR(500) COMMENT 联系地址, update_time DATETIME COMMENT 更新时间 ) UNIQUE KEY(user_id, username) DISTRIBUTED BY HASH(user_id) BUCKETS 4 PROPERTIES ( replication_allocation tag.location.default: 1, enable_unique_key_merge_on_write true );把这段存成 SQL 文件由你在自己的 Doris 环境里执行然后把SHOW CREATE TABLE的结果、报错原文或查询输出贴回 Codex 对话让它继续比对。不要让 Codex 直接连生产库替你执行。3.2 查导入批次和版本判断旧值属于哪一批旧值没被替换最容易忽略的就是「批次」。本地执行SHOW LOAD看最近的导入任务把 label、状态、时间对一遍确认新数据是不是真的进了同一个表、同一个分区。可以请 Codex 生成一组只读排查 SQL 模板SHOW LOAD FROM example_db ORDER BY CreateTime DESC LIMIT 20; SHOW PARTITIONS FROM example_db.user_profile; SHOW TABLET FROM example_db.user_profile;如果新数据是独立批次导入成功、批次时间晚于旧值那跨批次替换本应生效如果两行数据挤在同一批那顺序不保证就是预期行为。Codex 可以帮你读这些输出、解释每一列含义但执行和判断环境仍然在你这侧。3.3 写时合并开关与 compaction 状态确认写时合并是否真的在起作用要同时看表属性和 compaction。表属性里enable_unique_key_merge_on_write为true才走写时合并路径如果它是false或没设表可能仍按读时合并处理。另一方面即使开关打开物理合并也要等 compaction 推进短时间内查询读到的可能是尚未整理完的版本。把SHOW CREATE TABLE的结果、以及集群当前的 compaction 相关监控贴给 Codex让它给出「属性是否生效、还需要等多久、要不要手动触发」的对照表。这里要守住边界Codex 只解释和对照compaction 的触发命令由你在本地评估后执行。4. 三种数据模型分层对照别把 Unique 用错层4.1 DuplicateDWD 明细和 ADS 任意维度Duplicate 模型完全按导入文件存储两行完全相同也保留不做任何聚合列存优势能发挥出来。原文建议它在 DWD 层保存原始明细和历史在 ADS 层做任意维度聚合。建表时不写 Unique、Aggregate 或 Duplicate 时默认就是这个模型并自动指定排序列。排查 Unique 之前先想清楚这张表到底是要唯一主键还是只是想做明细追加——用错模型讨论「替换」本身就没意义。4.2 Aggregate固定报表和 REPLACE / SUM / MAX / MINAggregate 模型把列显式分成 Key 和 ValueKey 相同就聚合 Value聚合方式有 SUM、REPLACE、MAX、MIN 四种。它适合模式固定的报表查询预聚合能大幅减少扫描量但对COUNT(*)不友好Key 或记录数很多时查询也会变重。原文特别点出 REPLACE 的语义下一批数据替换之前导入的行中的值。这正好和 Unique 的读时合并同源所以排查 Unique 时Aggregate 的 REPLACE 规则是你理解批次行为的最好参照。4.3 UniqueODS 主键唯一1.2 后走向写时合并Unique 模型保证主键唯一但用不了 ROLLUP 这类预聚合特性原文因此把它定位在 ODS 层——初次全量加每天增量、需要唯一主键约束的场景。1.2 之前它等价于「全 REPLACE 的 Aggregate 表」1.2 引入写时合并后查询性能更好官方也把它作为未来默认方向两者短暂共存。把这张表放在 ODS、把明细留在 DWD、把固定报表交给 Aggregate是原文那条分层建议的落点。5. Codex 答复与 Doris 行为对不上时的排障5.1 Base URL 多写 /v1、模型 ID 编造这类配置错通道侧的错比较集中base_url写成https://taotoken.net/api/v1会直接出问题末尾就保留https://taotoken.net/api模型 ID 不要凭记忆拼去模型广场确认环境变量名要和env_key一致。还有一种情况是把别家工具的变量照搬进来Codex 只认自己的config.toml。这些配置错会让对话根本进不去和 Doris 的合并行为无关先分清楚是哪一层。5.2 把批次号、表结构、报错原文一起贴回对话当 Codex 说「跨批次应该替换」、而你查到的旧值仍在别只贴一句结论把SHOW CREATE TABLE、SHOW LOAD的结果、查询语句和实际输出一起贴回去明确告诉它这是本地执行后的结果。让它对照建表 Key 列、写时合并属性、批次时间重新推断。Codex 的价值在这里它帮你把 Doris 文档语义和你手头的证据对上而不是替你去集群里跑命令。6. 排查收尾把这次调用和结果对一下账走到这里你已经能用 Codex 解释 Unique 写时合并与读时合并的差异也能生成一套只读 SQL 在自己的 Doris 里核对建表语句和导入批次。接下来回到 TaoToken 模型对话 用同一把 Key 发一条测试消息确认模型 ID 和 Base URL 都没填错如果之后要长期靠它读建表语句、整理排查记录可以看一眼 Coding Plan 的额度是否够用。Key 本身在 控制台 API Keys 创建和管理Codex 这类命令行工具的接入参数也可以对照 Claude Code 接入文档 里的环境变量写法做迁移参考。回到 Doris 这件事本身遇到旧值没被替换先把批次边界、Key 列、写时合并属性和 compaction 四项排一遍多数「写时合并失败」最后都落在同一批次内的 REPLACE 顺序不保证这条语义上。Control 住这四步比反复重导数据有效得多。