跳到正文
RadarAI· Databricks:Blog(RSS)·· 3 小时前AI 评分34

Databricks 为 Unity Catalog Delta 表推出 UNIQUE 约束公开预览

AI 导读

Databricks 在 Databricks SQL 和 Databricks Runtime 18.2 及以上版本中,为 Unity Catalog Delta 表提供 UNIQUE 约束公开预览。一张表可声明多个 UNIQUE 约束、列可为空,外键也能引用这些约束;用 RELY 标记后,查询优化器可据此消除不必要的 join 和聚合。约束默认不强制执行,仅作为元数据记录。

正文 · AI 翻译

Databricks:Blog·2026-11-04 05:00· 26 分钟前AI 评分43

在 Databricks 上使用 UNIQUE 约束进行声明和优化

AI 导读

Databricks 在 Databricks SQL 和 Databricks Runtime 18.2 及以上版本中,为 Unity Catalog Delta 表提供 UNIQUE 约束公开预览。一张表可声明多个 UNIQUE 约束、列可为空,外键也能引用这些约束;用 RELY 标记后,查询优化器可据此消除不必要的 join 和聚合。约束默认不强制执行,仅作为元数据记录。

正文 · 原文

数据团队经常在管道中维护唯一性保证,但如果没有一种灵活的方式在表模式中表示所有这些保证,用户和工具就无法发现它们,查询优化器也无法安全地利用它们。业务实体通常有不止一个已知唯一的值或值组合:一张 users 表可能使用 user_id 作为其主行标识符,而其数据模型还记录了 username、电子邮件地址和 (workspace_id, external_user_id) 的单独唯一性保证。

Databricks 已支持 PRIMARY KEY 和 FOREIGN KEY 约束:主键标识表的行,外键可以引用它来描述关系。但是,一张表只能有一个主键,且其列必须为非空。UNIQUE 约束现已在 Databricks SQL 和 Databricks Runtime 18.2 及以上版本的 Unity Catalog Delta 表中提供公开预览,它增加了一种记录其他唯一性保证的方式。一张表可以声明多个 UNIQUE 约束,其列可以为空,外键可以引用它们。在验证这些约束并用 RELY 标记后,查询优化器还可以利用它们来消除不必要的 join 和聚合。

当一个主键无法覆盖所有唯一列时

PRIMARY KEY 和 UNIQUE 都记录唯一性,但它们承担不同的模式角色:

能力PRIMARY KEYUNIQUE
模式角色主行标识符附加唯一性保证
每张表的约束数一个多个
可空列否是

将管道的唯一性保证放入模式中

下表有一个主键和两个 UNIQUE 约束:

这两个唯一约束说明了不同类型的唯一性。users_external_uq 适用于列组合:每个 (workspace_id, external_user_id) 对必须唯一,即使其中任一值可能出现在其他对中。users_optional_id_uq 适用于可空列。以下行展示了哪些值满足该保证:

user_idoptional_identifier满足 UNIQUE (optional_identifier)?
1email:alice@example.com是
2NULL是
3NULL是
4email:bob@example.com是
5email:alice@example.com否 - 与第 1 行重复

记录保证而无需在每次写入时付出代价

与 Databricks 上的主键和外键约束一样,UNIQUE 约束是信息性的,在写入数据时不会强制执行。例如,声明 UNIQUE (optional_identifier) 不会导致包含重复非空值的写入失败。

这种分离让模式可以记录由摄取管道、上游系统或其他数据质量流程维护的唯一性保证。该保证还会成为表元数据,用户、工具和应用程序可以检查它。

从外键引用 UNIQUE 约束

外键除了可以引用主键外,还可以引用 UNIQUE 约束。例如,上面声明的复合 UNIQUE 约束可以按如下方式引用:

引用的父列标识外键所指向的唯一约束。子列与父列按位置对应:workspace_id 映射到 workspace_id,source_user_id 映射到 external_user_id。它们的名称无需匹配,但类型和位置必须对应。REFERENCES(父)列必须按照 UNIQUE 约束定义中列的顺序列出;否则约束仍然有效,但连接消除会静默地不触发。

要引用单列 UNIQUE 约束,请显式指定其父列,例如 REFERENCES users (username)。如果省略父列列表,REFERENCES users 会改为指向表的主键。

使用 RELY 优化查询

默认情况下,创建唯一约束时会带有 NORELY。它记录了预期的模式语义,但 Databricks 不会将其用于依赖正确性的查询重写。以下语句使用此默认值创建约束:

在验证数据并确保管道将继续维护该保证之后,你可以删除并使用 RELY 重新创建该约束:

在此示例中,CASCADE 还会删除任何引用 users_username_uq 的外键,必须在重新添加唯一约束后重新创建这些外键。如果没有 CASCADE,DROP CONSTRAINT 会使用默认的 RESTRICT 行为,并在存在依赖外键时被拒绝。

引用该唯一约束的外键也可以用 RELY 声明:

RELY 在每个约束上独立设置。它不会验证现有数据,也不会对未来的写入强制执行该约束。它允许 Databricks 在优化查询时假定该约束有效。如果数据违反了所依赖的约束,基于该约束的重写可能会产生错误结果。因此,你应仅对已验证并持续维护的保证使用 RELY。

借助 RELY,唯一约束可以提供符合条件的连接和聚合重写所需的信息。是否符合条件还取决于查询所需的其他保证,包括可空性,如下例所示。此优化支持在 启用 Photon 的计算上可用。主键和外键博客介绍了基于约束的连接和聚合优化,UNIQUE 约束也可以启用这些优化。自该文章发布以来,外键已支持 RELY,现在 UNIQUE 约束也已支持。

使用 UNIQUE 和外键约束消除连接

基于相同数据构建的一些仪表板需要来自 users 的属性,因此团队通常会创建一个共享视图,始终将 user_events 与 users 连接。仅需要来自 user_events 信息的仪表板仍可能从该视图继承该连接。因此,其生成的 SQL 可能如下面的查询所示,即使它没有从 users 中选择任何列:

尽管该查询没有从 users 返回任何列,优化器通常无法移除该连接。内连接可能会在不存在匹配用户时移除事件,或者在多个用户具有相同用户名时复制事件。

所依赖的约束排除了这两种可能性。UNIQUE 约束保证至多匹配一个用户,而外键保证匹配存在。由于 user_events.username 也是 NOT NULL,每个事件恰好有一个匹配。因此连接无法改变结果,优化器可以直接从 user_events 读取 event_id。

之前关于主键和外键的博客演示了相关的 LEFT OUTER JOIN 情况,该情况只需要至多一个的保证。这个 INNER JOIN 示例还需要外键来确立匹配存在。

在 NORELY 约束下,优化后的计划保留连接:

当两个约束都使用 RELY 时,优化器可以知道每个事件恰好有一个匹配用户,并移除连接:

在此示例中,user_events.username 是 NOT NULL,但当它可为空时该优化同样适用:Databricks 将移除连接并在 user_events.username 上添加 IS NOT NULL 过滤器,以保留内连接对空值的处理。

我们在 TPC-DS SF10000 数据集上对此优化进行了基准测试,该数据集代表 10 TB 规模。在 288 亿行的 store_sales 表与 6500 万行的 customer 表连接时,我们将 customer.c_customer_sk 声明为 UNIQUE,并从 store_sales.ss_customer_sk 定义了外键。在两个约束上都使用 RELY 后,Databricks 消除了连接,移除了超过 99.99% 的 shuffle,并将引擎执行时间中位数从 23.6 秒降至 3.0 秒——实现了 7.9 倍的加速。

当值已经唯一时跳过 DISTINCT

BI 工具和查询构建器经常防御性地添加 DISTINCT,因为连接可能产生重复行。当所选值已经唯一时,DISTINCT 不会移除任何内容,但除非优化器能证明该操作不必要,否则它仍可能需要聚合和 shuffle。所依赖的 UNIQUE 约束可以提供该证明。

考虑一个带有以下两个约束的 user_identifiers 表:

  • UNIQUE (user_id) RELY,其中 user_id 是 NOT NULL
  • UNIQUE (optional_identifier) RELY,其中 optional_identifier 可为空

该表包含以下数据,满足两个约束:

对于 user_id,优化器可以同时使用唯一性和非空保证:

优化后的计划不包含聚合:

聚合被移除,因为所依赖的约束确立了每个 user_id 已经是不同的。

对 optional_identifier 的等效查询展示了优化器为何也考虑可空性:

优化后的计划保留聚合:

两个约束都使用 RELY,但可空约束允许上面显示的两行 NULL。SQL DISTINCT 必须将这些行合并为一行,因此聚合仍然是必要的。

当查询提供缺失的非空保证时,可空的 UNIQUE 约束仍可启用该优化:

优化器可以移除聚合:

过滤器移除了在可空 UNIQUE 语义下可能重复的值。剩余的值保证是不同的,因此 Databricks 可以再次避免聚合。

我们在相同的 TPC-DS SF10000 数据集上对这项优化进行了基准测试,该数据集代表 10 TB 的规模因子。在包含 288 亿行的 store_sales 表上,我们将 (ss_item_sk, ss_ticket_number) 声明为复合 UNIQUE 约束,并针对这些列运行了一个 DISTINCT 查询。借助 RELY,Databricks 消除了 DISTINCT 聚合,移除了超过 99.99% 的 shuffle,并将引擎执行时间中位数从 33.7 秒降至 2.8 秒——实现了 12.0 倍的加速。

在优化器使用约束之前验证数据

查找重复行和孤立行

在使用 RELY 之前,请检查数据是否满足该保证。对于可空的 UNIQUE 约束,请排除包含 NULL 的行,因为 UNIQUE 允许这些行重复。以下查询用于查找重复的非空标识符:

对于复合 UNIQUE 约束,请按每个受约束的列进行分组,并为每个可空列添加一个 IS NOT NULL 条件。你还可以使用左反连接来查找没有匹配父行的子行:

这些检查用于验证当前数据。在启用 RELY 后,你的数据摄取或数据质量管道必须继续维护这些保证。

确认已声明的约束

在声明或重新创建约束后,你可以通过多种方式确认其定义:

  • SHOW CREATE TABLE
  • DESCRIBE TABLE EXTENDED
  • Unity Catalog INFORMATION_SCHEMA relations:
    • TABLE_CONSTRAINTS
    • KEY_COLUMN_USAGE
    • CONSTRAINT_TABLE_USAGE
    • CONSTRAINT_COLUMN_USAGE
    • REFERENTIAL_CONSTRAINTS

有关完整的创建、修改、检查和删除语法,请参阅 Databricks 上的约束 和 CONSTRAINT 子句。

逐步推出 UNIQUE 和 RELY

你可以逐步采用 UNIQUE:

  1. 使用默认的 NORELY 声明现有的唯一性保证。
  2. 在关系属于模式一部分的情况下,从外键引用唯一键。
  3. 在数据管道中验证并持续维护唯一性和引用完整性。
  4. 仅在验证唯一性之后,才对 UNIQUE 约束使用 RELY;仅在验证每个适用的子值都有匹配的父行之后,才对外键使用。
  5. 使用 EXPLAIN 比较计划以确认符合条件的重写。

结论

UNIQUE 约束允许表在其单个非空 PRIMARY KEY 之外记录多个唯一性保证,并且受约束的列可以为空。外键可以引用 UNIQUE 约束以及主键。UNIQUE 约束默认仅提供信息;在验证之后,RELY 允许 Databricks 使用它们来优化符合条件的连接和 DISTINCT 查询。

来源:Databricks:Blog · databricks.com

来源:RadarAI · aihot.news