行级并发

行级并发通过检测行级别的更改并自动解决并发写入更新或删除同一数据文件中的不同行时发生的冲突,从而减少并发写入操作之间的冲突。

行级并发要求

满足以下所有要求时,将自动启用行级并发:

  • 使用 Databricks Runtime 14.3 LTS 及更高版本。
  • 源表不使用分区。
  • 源表已启用删除向量。 请参阅 Databricks 中的删除向量

分区表不支持行级并发操作。 但是,在启用删除向量时,分区表仍然可以避免 OPTIMIZE 与写入操作之间的冲突。 请参阅行级别并发的限制

对于 14.3 LTS 之前的 Databricks Runtime 版本,请参阅 行级并发旧版行为

与行级并发控制相关的冲突矩阵

对于支持行级并发控制的源表,下表显示了在每个隔离级别下,哪些成对的写入操作可能发生冲突:

运算 INSERT (1) UPDATE删除 MERGE INTO OPTIMIZE
INSERT 无法冲突
UPDATE删除 MERGE INTO WriteSerializable 中不会出现冲突。 在可序列化事务中,修改同一行时可能会发生冲突。 修改同一行时可能发生冲突。
OPTIMIZE 无法冲突 使用 ZORDER BY 时,可能会发生冲突。 否则不会冲突。 使用 ZORDER BY 时,可能会发生冲突。 否则不会冲突。

(1) 此表中的所有 INSERT 操作都描述不包含从同一表读取数据的子查询的追加操作。 INSERT 包含从同一表读取数据的子查询的操作支持与 MERGE 相同的并发性。

注释

  • 包含标识列的表不支持并发事务。 请参阅标识列
  • REORG 操作的隔离语义与 OPTIMIZE 重写数据文件时完全相同。 使用 REORG 应用升级时,表协议会更改,这与所有正在进行的操作冲突。

缺乏行级并发时的写入冲突

对于未启用行级并发控制的源表,下表显示了在各个隔离级别下,哪些成对的写入操作可能发生冲突:

运算 INSERT (1) UPDATE删除 MERGE INTO OPTIMIZE
INSERT 无法冲突
UPDATE删除 MERGE INTO WriteSerializable 中不会出现冲突。 Serializable 中可能发生冲突。 请参阅 通过分区避免冲突 在 Serializable 和 WriteSerializable 中都可能出现冲突。 请参阅 通过分区避免冲突
OPTIMIZE 无法冲突 无法在启用删除向量的表中发生冲突,除非使用了ZORDER BY。 其他情况下,可能会出现冲突。 无法在启用删除向量的表中发生冲突,除非使用了ZORDER BY。 其他情况下,可能会出现冲突。

(1) 此表中的所有 INSERT 操作都描述不包含从同一表读取数据的子查询的追加操作。 INSERT 包含从同一表读取数据的子查询的操作支持与 MERGE 相同的并发性。

注释

  • 包含标识列的表不支持并发事务。 请参阅标识列
  • REORG 操作的隔离语义与 OPTIMIZE 重写数据文件时完全相同。 当你使用 REORG 执行升级时,表协议会发生变化,并与所有正在进行的操作产生冲突。

行级别并发的限制

行级并发存在限制。 对于以下操作,冲突解决遵循写入冲突的常规并发规则。 请参阅在没有行级别并发的情况下发生写入冲突

限度 说明
复杂条件子句 复杂数据类型(结构、数组、映射)、非确定性表达式、子查询和相关子查询的条件
MERGE 谓词要求 在 Databricks Runtime 14.2 中, MERGE 命令必须使用目标表上的显式谓词来筛选与源表匹配的行
性能权衡 行级冲突检测可能会增加执行总时间。 对于许多并发事务,编写器将延迟优先于冲突解决

删除向量的所有限制也适用。 请参阅限制

使用分区避免冲突

对于在冲突矩阵中标记为“可以冲突”的所有情况,仅当这两个操作影响同一组文件时,才会发生冲突。 要使两组文件互不重叠,请按操作条件中使用的相同列对表进行分区。

Example:

如果表未按日期进行分区,则命令 UPDATE table WHERE date > '2010-01-01' ...DELETE table WHERE date < '2010-01-01' 冲突,因为两者都可能尝试修改相同的文件。 对表进行分区,通过 date 避开冲突。

注释

将表按具有高基数的列分区可能会导致性能问题,因为子目录的数量庞大。

避免与显式分区筛选器冲突

此异常通常会在并发执行 DELETEUPDATEMERGE 操作期间引发,即使这些操作更新的是不同的分区,也可能会读取同一分区。 在操作条件中明确分离:

// Problem: Condition can scan the entire table
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

// Solution: Add explicit partition filters
deltaTable.as("t").merge(
    source.as("s"),
    "s.user_id = t.user_id AND s.date = t.date AND s.country = t.country AND t.date = '" + date + "' AND t.country = '" + country + "'")
  .whenMatched().updateAll()
  .whenNotMatched().insertAll()
  .execute()

冲突异常

发生事务冲突时,您可能会遇到以下异常之一:

ConcurrentAppendException

当并发操作在操作读取的同一分区(或未分区表中的任何位置)中添加文件时,会发生此异常。 文件添加操作可能是由 INSERTDELETEUPDATEMERGE 操作引起的。

使用默认的 WriteSerializable 隔离级别时,由 INSERT 操作添加且仅追加数据而不读取任何数据的文件,不会与任何操作发生冲突。 如果隔离级别是可序列化的,则任何追加可能会冲突。

重要

INSERT 操作在 WriteSerializable 模式下可能会发生冲突,如果多个并发的 DELETEUPDATEMERGE 操作可能引用由 INSERT 操作追加的值。 要避免此情况:

  • 确保并发 DELETEUPDATEMERGE 操作不会读取追加的数据
  • 至多有一个 DELETEUPDATEMERGE 操作可以读取追加的数据

ConcurrentDeleteReadException

当并发操作删除操作读取的文件时,会发生此异常。 常见原因是DELETEUPDATEMERGE等操作会重写文件。

ConcurrentDeleteDeleteException

当并发操作删除操作也删除的文件时,会发生此异常。 这可能是由于两个并发压缩操作重写相同的文件引起的。

MetadataChangedException(元数据更改异常)

当并发事务更新 Delta 表的元数据时,将发生此异常。 常见原因包括更新表架构的 ALTER TABLE 操作或写入。

ConcurrentTransactionException

如果同时启动使用相同检查点位置的流式处理查询并尝试同时写入 Delta 表,则会发生此异常。 永远不要同时运行具有相同检查点位置的两个流式处理查询。

ProtocolChangedException

在以下情况下,可能会出现此异常:

  • 您的 Delta Lake 表已升级到新的协议版本(您可能需要升级 Databricks Runtime)
  • 多个写入者同时创建或替换表
  • 多个写入者同时向空路径写入数据

请参阅 Delta Lake 功能兼容性和协议

行级并发旧版行为

在 Databricks Runtime 13.3 LTS 中,行级并发使用旧行为:

其他资源