3、创建 Delta 表:使用 Spark SQL 创建表、使用 DataFrame API 创建表、管理表的 Location 和 Properties

创建 Delta 表,说白了就是把数据湖里的文件,变成一张能查能改的“表”。

我刚开始接触 Delta Lake 时,觉得这不就是个 parquet 加了个壳吗?后来才发现,这个“壳”带来的 ACID 事务和 schema 管理,简直是救命稻草。今天咱们就聊聊怎么创建 Delta 表,以及怎么管好它的“家”和“脾气”。

核心要点: Delta 表本质上是一个目录,里面存着 parquet 文件和 _delta_log 事务日志。创建表就是告诉 Spark:“嘿,把这个目录当成一张表来管理。”

3.1 使用 Spark SQL 创建表

Spark SQL 创建 Delta 表,和创建普通 Hive 表很像。但有个关键区别——你得指定 USING delta。

我个人习惯先用 SQL 建表,因为 DDL 语句清晰,团队 review 也方便。举个例子:

-- 创建托管表(Managed Table)
CREATE TABLE IF NOT EXISTS sales_data (
  order_id STRING,
  customer_id STRING,
  product_id STRING,
  quantity INT,
  price DECIMAL(10,2),
  order_date DATE
)
USING delta
PARTITIONED BY (order_date)
LOCATION '/user/hive/warehouse/sales_data'
TBLPROPERTIES (
  'delta.enableChangeDataFeed' = 'true',
  'delta.minReaderVersion' = '2',
  'delta.minWriterVersion' = '5'
);

这里要注意几个点:

  • USING delta 是必须的,否则 Spark 默认用 parquet 格式。
  • PARTITIONED BY 按日期分区,查询时能大幅减少扫描数据量。
  • LOCATION 指定表数据存储路径。如果不写,Spark 会放到默认的 warehouse 目录下。
  • TBLPROPERTIES 用来设置 Delta 表的特性,比如开启 Change Data Feed。
我的经验: 生产环境中,我强烈建议显式指定 LOCATION。默认路径一旦集群配置变了,表可能就找不到了。我曾经踩过这个坑,迁移集群时一堆表变成“孤儿”,重建 metadata 花了一整天。

3.2 使用 DataFrame API 创建表

如果你在写 ETL 作业,用 DataFrame API 创建表会更灵活。你可以在数据转换完成后,直接保存为 Delta 表。

看个例子:

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DecimalType, DateType

spark = SparkSession.builder \
    .appName("CreateDeltaTable") \
    .config("spark.sql.extensions", "io.delta.sql.DeltaSparkSessionExtension") \
    .config("spark.sql.catalog.spark_catalog", "org.apache.spark.sql.delta.catalog.DeltaCatalog") \
    .getOrCreate()

# 定义 schema
schema = StructType([
    StructField("order_id", StringType(), True),
    StructField("customer_id", StringType(), True),
    StructField("product_id", StringType(), True),
    StructField("quantity", IntegerType(), True),
    StructField("price", DecimalType(10,2), True),
    StructField("order_date", DateType(), True)
])

# 创建空 DataFrame
df = spark.createDataFrame([], schema)

# 保存为 Delta 表
df.write \
    .format("delta") \
    .mode("overwrite") \
    .partitionBy("order_date") \
    .option("path", "/user/hive/warehouse/sales_data") \
    .saveAsTable("sales_data")

这里有几个关键点:

  • saveAsTable 会在 Hive metastore 中注册表,同时把数据写到指定路径。
  • mode("overwrite") 会覆盖已有表。第一次建表时用 overwrite 没问题,但后续更新数据时,我建议用 append 或 merge。
  • option("path", ...) 指定存储路径,和 SQL 中的 LOCATION 作用一样。
注意: 如果你用 df.write.format("delta").save("/path") 而不调用 saveAsTable,Spark 只会写文件,不会在 metastore 中注册表。这样你就没法用 SQL 查了。我见过不少新手在这上面栽跟头。

3.3 管理表的 Location 和 Properties

表建好了,怎么管?Location 和 Properties 是两大核心。

3.3.1 Location 管理

Delta 表的 Location 就是数据文件存放的目录。你可以用 DESCRIBE EXTENDED 查看:

DESCRIBE EXTENDED sales_data;

输出中会包含 Location 字段,告诉你数据实际存在哪。

如果你想移动表数据,千万别直接拷文件!Delta 表有事务日志,文件路径变了,日志里记录的路径还是旧的。正确的做法是:

  1. 用 ALTER TABLE ... SET LOCATION 更新元数据。
  2. 或者用 FSCK REPAIR TABLE 修复分区信息。

我曾经手贱,直接 hdfs dfs -mv 移动了一个 Delta 表目录,结果表直接废了。后来花了半天重建事务日志,血的教训。

3.3.2 Properties 管理

Delta 表的 Properties 控制着表的行为。常用的有:

属性名 作用 推荐值
delta.enableChangeDataFeed 开启变更数据捕获(CDF) true / false
delta.minReaderVersion 最低读取器版本 1 或 2
delta.minWriterVersion 最低写入器版本 2 到 7
delta.autoOptimize.optimizeWrite 自动优化写入 true
delta.autoOptimize.autoCompact 自动小文件合并 true
delta.targetFileSize 目标文件大小(字节) 134217728 (128MB)

设置属性的语法:

-- 修改表属性
ALTER TABLE sales_data SET TBLPROPERTIES (
  'delta.autoOptimize.optimizeWrite' = 'true',
  'delta.autoOptimize.autoCompact' = 'true'
);

-- 查看当前属性
SHOW TBLPROPERTIES sales_data;
我的建议: 对于频繁写入的表,一定要开启 delta.autoOptimize.optimizeWrite 和 delta.autoOptimize.autoCompact。否则小文件会越来越多,查询性能直线下降。我有个表没开这个,半年后文件数从 100 个涨到 10 万个,查询从秒级变成了分钟级。

3.4 知识体系总览

下面这张图总结了创建 Delta 表的三种方式以及核心管理要素:

Delta 表创建与管理知识体系 Delta 表创建与管理 Spark SQL 创建 DataFrame API 创建 管理 Location & Properties USING delta 指定格式 PARTITIONED BY 分区 LOCATION 指定路径 saveAsTable 注册表 mode 控制写入模式 option("path", ...) 路径 DESCRIBE EXTENDED 查看 ALTER TABLE SET LOCATION TBLPROPERTIES 配置 💡 核心原则:Location 决定数据存哪,Properties 决定表怎么工作 创建时规划好,后期少踩坑

3.5 实战建议

最后,分享几个我在项目中总结的经验:

  • 创建表时就把 Properties 配好,别等出问题了再改。比如开启 CDF,如果表已经写了很多数据,再开启可能会丢失历史变更记录。
  • 分区字段要选对。按日期分区是常见做法,但如果你有大量按地区查询的场景,也可以考虑按地区分区。分区字段的基数不要太大,否则会产生大量小目录。
  • Location 路径要有规范。我习惯用 /data/{项目}/{表名} 的格式,这样不同项目的数据不会混在一起。
  • 用 DataFrame API 创建表时,记得先定义好 schema。让 Spark 推断 schema 虽然方便,但字段类型可能不是你想要的。比如整数被推断成 long,日期被推断成 string,后面查起来很麻烦。
避坑指南: 我曾经在创建表时忘了写 USING delta,结果 Spark 默认用了 parquet。后来想用 Delta 的特性,发现表不支持,只能重建。所以,建表语句一定要仔细检查。

嗯,创建 Delta 表其实不难,但细节决定成败。把 Location 和 Properties 管好了,后面的数据治理工作会顺畅很多。