使用架构演变为表架构更新

表支持架构演变,允许在数据要求发生变化时修改表结构。 支持下列类型的更改:

使用 DDL 显式进行这些更改,或使用 DML 隐式进行这些更改。

Important

架构更新与所有并发写入操作冲突。 Databricks 建议协调架构更改,以避免写入冲突。

更新表架构会终止从该表读取的任何流。 若要继续处理,请使用 结构化流的生产注意事项中所述的方法重启流。

手动架构变更

使用 ALTER TABLE 语句显式更改表的架构,而无需写入新数据。

添加列

用于 ALTER TABLE ... ADD COLUMNS 向现有表添加一个或多个列,可以选择指定位置和注释:

ALTER TABLE table_name ADD COLUMNS (col_name data_type [COMMENT col_comment] [FIRST|AFTER colA_name], ...)

默认情况下,可空性为 true

示例:添加嵌套字段

仅支持为结构添加嵌套列。 不支持数组和映射。

若要将列添加到嵌套字段,请使用:

ALTER TABLE table_name ADD COLUMNS (col_name.nested_col_name data_type [COMMENT col_comment] [FIRST|AFTER colA_name], ...)

例如,如果运行 ALTER TABLE boxes ADD COLUMNS (colB.nested STRING AFTER field1) 之前的架构为:

- root
| - colA
| - colB
| +-field1
| +-field2

则运行之后的架构为:

- root
| - colA
| - colB
| +-field1
| +-nested
| +-field2

更改列注释和排序

使用 ALTER TABLE ... ALTER COLUMN 可更新列注释,或调整该列相对于其他列的顺序:

ALTER TABLE table_name ALTER [COLUMN] col_name (COMMENT col_comment | FIRST | AFTER colA_name)

示例:更改嵌套字段

若要更改嵌套字段中的列,请使用:

ALTER TABLE table_name ALTER [COLUMN] col_name.nested_col_name (COMMENT col_comment | FIRST | AFTER colA_name)

例如,如果运行 ALTER TABLE boxes ALTER COLUMN colB.field2 FIRST 之前的架构为:

- root
| - colA
| - colB
| +-field1
| +-field2

则运行之后的架构为:

- root
| - colA
| - colB
| +-field2
| +-field1

替换列

用于 ALTER TABLE ... REPLACE COLUMNS 在单个操作中重新定义表的完整列列表,包括添加、删除、重新排序或重命名列:

ALTER TABLE table_name REPLACE COLUMNS (col_name1 col_type1 [COMMENT col_comment1], ...)

示例:替换嵌套字段

例如,运行以下 DDL 时:

ALTER TABLE boxes REPLACE COLUMNS (colC STRING, colB STRUCT<field2:STRING, nested:STRING, field1:STRING>, colA STRING)

如果之前的模式是:

- root
| - colA
| - colB
| +-field1
| +-field2

则运行之后的架构为:

- root
| - colC
| - colB
| +-field2
| +-nested
| +-field1
| - colA

重命名列

若要重命名列且不重写任何列的现有数据,必须为表启用列映射。 请参阅 有关使用 Delta Lake 列映射重命名和删除列的说明

要重命名列,请执行以下步骤:

ALTER TABLE table_name RENAME COLUMN old_col_name TO new_col_name

示例:重命名嵌套字段

重命名嵌套字段:

ALTER TABLE table_name RENAME COLUMN col_name.old_nested_field TO new_nested_field

例如,运行以下命令时:

ALTER TABLE boxes RENAME COLUMN colB.field1 TO field001

如果先前的架构是:

- root
| - colA
| - colB
| +-field1
| +-field2

接下来的架构为:

- root
| - colA
| - colB
| +-field001
| +-field2

请参阅 有关使用 Delta Lake 列映射重命名和删除列的说明

删除字段

必须为表启用列映射,以便在无需重写任何数据文件的情况下通过元数据操作删除列。 请参阅 有关使用 Delta Lake 列映射重命名和删除列的说明

注释

从元数据中删除列不会删除文件中该列的基础数据。 要清除已删除列的数据:

  • 使用 REORG TABLE 重写文件。
  • 然后,用于 VACUUM 以物理方式删除包含已删除的列数据的文件。

删除一列:

ALTER TABLE table_name DROP COLUMN col_name

删除多列:

ALTER TABLE table_name DROP COLUMNS (col_name_1, col_name_2)

更改列类型或名称

可以通过重写表来更改列的类型或名称或者删除列。 为此,请使用 overwriteSchema 选项。

以下示例演示如何更改列类型:

(spark.read.table(...)
  .withColumn("birthDate", col("birthDate").cast("date"))
  .write
  .mode("overwrite")
  .option("overwriteSchema", "true")
  .saveAsTable(...)
)

以下示例演示如何更改列名称:

(spark.read.table(...)
  .withColumnRenamed("dateOfBirth", "birthDate")
  .write
  .mode("overwrite")
  .option("overwriteSchema", "true")
  .saveAsTable(...)
)

启用架构演变

使用 WITH SCHEMA EVOLUTION 或将 mergeSchema 设置为 true,根据要 INSERTMERGE 到现有表的数据架构进行架构更改。

使用以下方法之一启用架构演变:

  • 对语句使用INSERT WITH SCHEMA EVOLUTION语法INSERT
  • MERGE WITH SCHEMA EVOLUTION 在 SQL 语法中使用 WITH SCHEMA EVOLUTION,或在 Azure Databricks API 中使用 .withSchemaEvolution()
  • mergeSchema设置批处理写入或流式写入的选项。 针对单个写入操作设置 .option("mergeSchema", "true")
  • 设置 Spark 配置(旧版):spark.databricks.delta.schema.autoMerge.enabled设置为true以应用于整个 SparkSession。

Databricks 建议使用 WITH SCHEMA EVOLUTION 语法或 mergeSchema 选项(而不是设置 Spark 配置)为每个写入操作启用架构演变。

使用选项或语法在写入操作中启用架构演变时,该设置优先于 Spark 配置。

为写入操作启用架构演变以添加新列

启用架构演变后,源查询中存在的但目标表中缺少的列会自动添加为写入事务的一部分。 请参阅启用架构演变

考虑以下情况:

  • 追加新列时,会保留大小写。
  • 新列将添加到表架构的末尾。
  • 如果其他列位于结构中,它们将被追加到目标表中结构的末尾。

INSERT 使用 SQL 进行架构演变

在 Databricks Runtime 18.1 及更高版本中,使用 WITH SCHEMA EVOLUTION 语句中的 INSERT 子句启用架构演变:

INSERT WITH SCHEMA EVOLUTION INTO target_table
SELECT * FROM source_table

如果查询 source_table 返回目标表中不存在的列,这些列将自动添加到 target_table 架构中。 现有行接收 NULL 新列的值。

WITH SCHEMA EVOLUTION 子句支持 INSERT INTOINSERT OVERWRITEINSERT INTO ... REPLACE 形式。 目标必须是 Delta Lake 或 Apache Iceberg 表。 在插入到 Hive 或其他非 Delta 表时使用此子句会返回错误。

如果使用 Databricks Runtime 18.0 或更低版本,请改为使用 mergeSchema 此选项启用架构演变。 请参阅 INSERT 使用 DataFrame API 的架构演变

INSERT 使用 DataFrame API 进行架构演变

以下示例演示如何将 mergeSchema 选项与批处理写入操作配合使用:

Python
(spark.read
  .table("source_table")
  .write
  .option("mergeSchema", "true")
  .mode("append")
  .saveAsTable("target_table")
)
Scala
spark.read
  .table("source_table")
  .write
  .option("mergeSchema", "true")
  .mode("append")
  .saveAsTable("target_table")

INSERT 借助 Structured Streaming 实现模式演变

以下示例演示了在结构化流处理中使用 Auto Loader 时如何配合 mergeSchema 选项。 请参阅什么是自动加载程序?

(spark.readStream
  .format("cloudFiles")
  .option("cloudFiles.format", "json")
  .option("cloudFiles.schemaLocation", "<path-to-schema-location>")
  .load("<path-to-source-data>")
  .writeStream
  .option("mergeSchema", "true")
  .option("checkpointLocation", "<path-to-checkpoint>")
  .trigger(availableNow=True)
  .toTable("table_name")
)

合并的自动模式演变

对于 MERGE,架构演变可帮助您解决目标表和源表之间的架构不匹配问题。 此功能可处理以下两种情况:

  1. 列存在于源表中但不存在于目标表中,并且在插入或更新操作的赋值中按名称指定。 或者,存在 UPDATE SET *INSERT * 操作。

    该列将添加到目标架构,并且将从源中的相应列填充其值。

    • 这仅适用于合并源中的列名称和结构与目标分配完全匹配时。

    • 新列必须存在于源架构中。 在 action 子句中分配新列不会定义该列。

    这些示例允许架构演变:

    -- The column newcol is present in the source but not in the target. It will be added to the target.
    UPDATE SET target.newcol = source.newcol
    
    -- The field newfield doesn't exist in struct column somestruct of the target. It will be added to that struct column.
    UPDATE SET target.somestruct.newfield = source.somestruct.newfield
    
    -- The column newcol is present in the source but not in the target.
    -- It will be added to the target.
    UPDATE SET target.newcol = source.newcol + 1
    
    -- Any columns and nested fields in the source that don't exist in target will be added to the target.
    UPDATE SET *
    INSERT *
    

    如果架构中newcol不存在列source,则这些示例不会触发架构演变:

    UPDATE SET target.newcol = source.someothercol
    UPDATE SET target.newcol = source.x + source.y
    UPDATE SET target.newcol = source.output.newcol
    
  2. 目标表中存在列,但不存在源表。

    目标架构不会更改。 这些列:

    • 对于 UPDATE SET *,保持不变。

    • 对于 NULL,设置为 INSERT *

    • 如果在动作子句中被赋值,仍可能被显式修改。

    例如:

    UPDATE SET *  -- The target columns that are not in the source are left unchanged.
    INSERT *  -- The target columns that are not in the source are set to NULL.
    UPDATE SET target.onlyintarget = 5  -- The target column is explicitly updated.
    UPDATE SET target.onlyintarget = source.someothercol  -- The target column is explicitly updated from some other source column.
    

必须手动启用自动架构演变。 请参阅启用架构演变

注释

在 Databricks Runtime 11.3 LTS 及以下版本中,只有 INSERT *UPDATE SET * 操作可用于通过合并进行架构演变。

在 Databricks Runtime 12.2 LTS 及更高版本中,可以在插入或更新操作中按名称指定源表中的列和结构字段。

在 Databricks Runtime 13.3 LTS 及更高版本中,可以将模式演变与在映射中嵌套的结构一起使用,例如 map<int, struct<a: int, b: int>>

MERGE使用 SQL、Python 和 Scala 进行架构演变

在 Databricks Runtime 15.4 LTS 及更高版本中,可以使用 SQL 或表 API 在合并语句中指定架构演变:

SQL
MERGE WITH SCHEMA EVOLUTION INTO target
USING source
ON source.key = target.key
WHEN MATCHED THEN
  UPDATE SET *
WHEN NOT MATCHED THEN
  INSERT *
WHEN NOT MATCHED BY SOURCE THEN
  DELETE
Python
from delta.tables import *

(targetTable
  .merge(sourceDF, "source.key = target.key")
  .withSchemaEvolution()
  .whenMatchedUpdateAll()
  .whenNotMatchedInsertAll()
  .whenNotMatchedBySourceDelete()
  .execute()
)
Scala
import io.delta.tables._

targetTable
  .merge(sourceDF, "source.key = target.key")
  .withSchemaEvolution()
  .whenMatched()
  .updateAll()
  .whenNotMatched()
  .insertAll()
  .whenNotMatchedBySource()
  .delete()
  .execute()

使用架构演变的示例操作MERGE

以下示例展示了在有架构演变和没有架构演变的情况下 MERGE 操作的效果。

Columns 查询(在 SQL 中) 无架构演变的行为(默认值) 有架构演变的行为
目标列:key, value
源列:key, value, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET *
WHEN NOT MATCHED
THEN INSERT *
表架构保持不变;仅更新/插入列 keyvalue 表架构更改为 (key, value, new_value)。 使用源中的 valuenew_value 更新具有匹配项的现有记录。 使用架构 (key, value, new_value) 插入新行。
目标列:key, old_value
源列:key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET *
WHEN NOT MATCHED
THEN INSERT *
UPDATEINSERT 操作会引发错误,因为目标列 old_value 不在源中。 表架构更改为 (key, old_value, new_value)。 具有匹配项的现有记录会使用源中的 new_value 进行更新,而 old_value 保持不变。 使用 key 的指定 new_valueNULLold_value 插入新记录。
目标列:key, old_value
源列:key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN MATCHED
THEN UPDATE SET new_value = s.new_value
UPDATE 引发错误,因为目标表中不存在列 new_value 表架构更改为 (key, old_value, new_value)。 使用源中的 new_value 更新具有匹配项的现有记录,old_value 保持不变,不匹配的记录包含针对 NULL 输入的 new_value。 参阅注释 (1)
目标列:key, old_value
源列:key, new_value
MERGE INTO target_table t
USING source_table s
ON t.key = s.key
WHEN NOT MATCHED
THEN INSERT (key, new_value) VALUES (s.key, s.new_value)
INSERT 引发错误,因为目标表中不存在列 new_value 表架构更改为 (key, old_value, new_value)。 使用 key 的指定 new_valueNULLold_value 插入新记录。 现有记录包含针对 NULL 输入的 new_value,而 old_value 保持不变。 参阅注释 (1)

(1) 此行为在 Databricks Runtime 12.2 LTS 及更高版本中可用;在这种情况下,Databricks Runtime 11.3 LTS 及更低版本出错。

排除已合并的列

在 Databricks Runtime 12.2 LTS 及更高版本中,可以在合并条件中使用 EXCEPT 子句显式排除列。 EXCEPT 关键字的行为因是否启用架构演变而异。

关闭架构演变后,关键字 EXCEPT 将应用于目标表中的列列表,并允许从 UPDATEINSERT 操作中排除列。 被排除的列被设置为 null

启用架构演变后,EXCEPT 关键字将应用于源表中的列列表,并允许从架构演变中排除列。 源表中的新列不存在于目标表中,如果它在 EXCEPT 子句中列出,则不会添加到目标架构中。 目标中已存在的排除列设置为 null

结合使用 EXCLUDEMERGE 的示例

以下示例演示了这些语法:

Columns 查询(在 SQL 中) 无架构演变的行为(默认值) 有架构演变的行为
目标列:id, title, last_updated
源列:id, title, review, last_updated
MERGE INTO target t
USING source s
ON t.id = s.id
WHEN MATCHED
THEN UPDATE SET last_updated = current_date()
WHEN NOT MATCHED
THEN INSERT * EXCEPT (last_updated)
通过将 last_updated 字段设置为当前日期来更新匹配的行。 使用 idtitle 的值插入新行。 排除的字段 last_updated 设置为 nullreview 字段被忽略,因为它不在目标中。 通过将 last_updated 字段设置为当前日期来更新匹配的行。 架构已演变为添加 review 字段。 使用除 last_updated 设置为 null 外的所有源字段插入新行。
目标列:id, title, last_updated
源列:id, title, review, internal_count
MERGE INTO target t
USING source s
ON t.id = s.id
WHEN MATCHED
THEN UPDATE SET last_updated = current_date()
WHEN NOT MATCHED
THEN INSERT * EXCEPT (last_updated, internal_count)
INSERT 引发错误,因为目标表中不存在列 internal_count 通过将 last_updated 字段设置为当前日期来更新匹配的行。 review 字段将添加到目标表,但忽略该 internal_count 字段。 插入的新行已将 last_updated 设置为 null

通过 Spark 配置启用架构演变(旧版)

可以将 Spark 配置 spark.databricks.delta.schema.autoMerge.enabled 设置为 true 启用当前 SparkSession 中所有写入作的架构演变:

Python

spark.conf.set("spark.databricks.delta.schema.autoMerge.enabled", True)

Scala

spark.conf.set("spark.databricks.delta.schema.autoMerge.enabled", true)

SQL

SET spark.databricks.delta.schema.autoMerge.enabled=true

注释

Databricks 不建议使用此方法进行生产。 设置会话范围的配置可能会导致跨多个操作发生意外的架构更改,并更难推断哪些操作会演变架构。

而是为每个写入操作启用架构演变:

使用选项或语法在写入操作中启用架构演变时,该设置优先于 Spark 配置。

替换表架构

默认情况下,覆盖表中的数据不会覆盖架构。 当使用 mode("overwrite") 覆盖表而不使用 replaceWhere 时,你可能仍想覆盖正在写入的数据的架构。

若要替换表的架构和分区,请将 overwriteSchema 选项设置为 true

df.write.option("overwriteSchema", "true")

注释

使用动态分区覆盖时,不能将 overwriteSchema 指定为 true。 请参阅使用 partitionOverwriteMode 的动态分区覆盖(旧版)