很多数据团队在管道里维护着多种唯一性保证——比如用户表里,user_id 是主键,但 username、email、以及 (workspace_id, external_user_id) 的组合也都要求唯一。可问题是,传统表结构只能表达一个主键,这些额外的唯一性只能靠外部逻辑和文档维护,查询优化器看不到它们,自然也不敢用它做任何改写。结果就是,BI 工具为了防重复总加 DISTINCT,视图里多余的 JOIN 每次都真跑一遍,数据量一大就白白烧掉几十分钟。
Databricks 最近在 Unity Catalog Delta 表的 Public Preview(Databricks SQL 和 Runtime 18.2 以上)中加入了 UNIQUE 约束,补上了这个短板。它和 PRIMARY KEY 定位不同:主键只能一个、列必须非空;UNIQUE 可以多个、列允许可空。一个表可以同时声明多个 UNIQUE,外键也能引用它们。更关键的是,当你验证过数据、用 RELY 标记这些约束后,优化器就能利用它们做查询改写,直接消掉不必要的 JOIN 和 DISTINCT 聚合。
先看 JOIN 消除。很多团队会在视图里固定把 user_events 和 users 联表,下游仪表盘可能根本不需要 users 的字段,但生成 SQL 里这个 JOIN 还在。理论上,内连接可能因为匹配不到用户删掉事件行,也可能由于 username 重复而翻倍行数。如果 users.username 有 UNIQUE 约束,且 user_events.username 也有外键引用它,再加上 user_events.username 非空,那每一行事件必然恰好匹配一个用户——JOIN 的结果不会改变,优化器就可以安全地把 JOIN 删掉。实测在 TPC-DS SF10000(10 TB,store_sales 288 亿行,customer 6500 万行)上,对 customer.c_customer_sk 声明 UNIQUE,再让 store_sales.ss_customer_sk 外键引用它,两边都加 RELY 后,JOIN 被消除,shuffle 少了 99.99%,引擎执行中位数时间从 23.6 秒降到 3.0 秒,提速 7.9 倍。注意,即使外键列可空,优化也会追加 IS NOT NULL 过滤来保留内连接语义。
再看 DISTINCT 消除。BI 工具经常因为怕 JOIN 产生重复而加 DISTINCT,但数据本来就唯一时,DISTINCT 纯粹是浪费。如果对某列声明了 UNIQUE RELY 且该列非空,优化器就能证明每个值都独立,直接去掉聚合。实测同样的 TPC-DS 库,对 store_sales 的 (ss_item_sk, ss_ticket_number) 复合 UNIQUE 约束,跑 DISTINCT 查询,去掉聚合后 shuffle 减少 99.99%,中位执行时间从 33.7 秒降到 2.8 秒,提速 12 倍。可空列的情况更有意思:UNIQUE 允许 NULL 重复,所以只靠约束不能证明 DISTINCT 无用。但如果你在查询里加上 WHERE optional_identifier IS NOT NULL,优化器就能利用约束和过滤条件,判断剩余值必然唯一,从而同样去除聚合。
背后原理不复杂:约束默认是 NORELY,只记录语义不做安全改写。当你验证了数据、确认管道会持续维护这些保证后,可以 DROP 再 CREATE 加 RELY(或 ALTER),外键也能单独设 RELY。RELY 不会校验历史数据也不会强制写入,它只是让优化器“信任”这份声明——一旦数据违反,重启后的结果就可能错误,所以必须确认有持续的数据质量流程。验证方法很简单:对可空 UNIQUE 列查重复非空值,对复合 UNIQUE 按所有列 GROUP BY 再加 IS NOT NULL 条件,外键关系用 left anti join 查孤儿行。
这个能力对任何做大宽表、多键关联、BI 加速的团队都值得一试。它把“数据团队已经知道的事实”显式变成元数据,让优化器不再盲猜。采纳时可以循序渐进:先用 NORELY 声明,EXPLAIN 看看是否命中,验证数据后再给 UNIQUE 和外键加 RELY。要注意外键引用 UNIQUE 时,REFERENCES 的列顺序必须与约束定义完全一致,否则约束合法但优化不触发。还有个易错点:如果省略父列列表,REFERENCES users 默认指向主键,想指到 UNIQUE 列必须显式列名。此外,用 CASCADE 删约束会连坐外键,需重建。
总的来说,这是一个把“数据事实”转化为“查询加速”的典型例子——不是新算法,而是让优化器获得本该拥有的元数据。对于每天跑大量冗余 JOIN 和 DISTINCT 的报表管道,几个约束声明就能省下数分钟和一大笔集群成本,很划算。
内容与图片版权归原作者所有 · 原文: https://www.databricks.com/blog/declare-and-optimize-unique-constraints-databricks