创建第一个 Delta 表:三种方式,一个核心

好,咱们正式开始动手了。前面两章我们把 Delta Lake 的来龙去脉和核心概念捋了一遍,说实话,光看不练容易忘。这一章我带你亲手创建第一个 Delta 表,而且我会用三种不同的方式来做——Spark SQL、DataFrame API,还有指定分区和存储路径的进阶玩法。

你可能会问:「有必要学三种吗?」嗯,我个人习惯是:开发调试用 DataFrame API,生产运维用 Spark SQL,性能优化靠分区设计。三种方式各有各的用武之地,咱们一个一个来。

方式一:用 Spark SQL 创建 Delta 表

Spark SQL 是最直观的方式,说白了就是写 SQL。如果你团队里有数据分析师,他们肯定喜欢这种方式。

核心语法:在 Spark 3.x 中,创建 Delta 表只需要在 CREATE TABLE 语句里指定 USING DELTA。

-- 创建一张用户行为日志表
CREATE TABLE IF NOT EXISTS user_events (
    event_id STRING,
    user_id STRING,
    event_type STRING,
    event_time TIMESTAMP,
    page_url STRING
)
USING DELTA
LOCATION '/data/delta/user_events'

这段 SQL 执行完后,Delta 表就建好了。你可能会问:「表在哪里?」我告诉你,Delta 表其实就是一个目录,里面存着 Parquet 数据文件和 _delta_log 目录。你可以用 DESCRIBE EXTENDED user_events 查看表的详细信息。

小提示:IF NOT EXISTS 建议每次都加上。我在生产环境见过因为重复执行建表语句导致任务失败的情况,加上这个关键字能避免很多麻烦。

方式二:用 DataFrame API 创建 Delta 表

如果你是个 Scala 或 Python 开发者,DataFrame API 可能更顺手。我个人更喜欢这种方式,因为可以在代码里动态控制表的结构和路径。

from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, TimestampType

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("event_id", StringType(), True),
    StructField("user_id", StringType(), True),
    StructField("event_type", StringType(), True),
    StructField("event_time", TimestampType(), True),
    StructField("page_url", StringType(), True)
])

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

# 写入为 Delta 表
empty_df.write.format("delta").save("/data/delta/user_events_df")

这段代码创建了一个空表。你可能会觉得「空表有什么用?」别急,实际项目中我们通常先创建表结构,然后用 append 模式不断写入数据。

注意:用 DataFrame API 创建表时,默认不会在 Hive 元数据中注册。如果你想让 Spark SQL 也能查到这张表,需要加上 .saveAsTable("user_events_df")。

方式三:指定分区与存储路径

好了,前两种方式算是热身。接下来这个才是重头戏——分区。我在项目中遇到过一个大坑:一开始没做分区设计,结果一张表跑了半年,数据量到了几十 TB,每次查询都要全表扫描,慢得让人抓狂。

分区说白了就是按某个字段把数据拆成多个子目录。查询时只扫描相关分区,性能能提升几十倍。

分区字段怎么选?

我建议遵循两个原则:

  • 查询频率高:你经常在 WHERE 条件里用的字段
  • 基数适中:分区字段的值不能太多也不能太少。比如按天分区(一年 365 个分区)就比按小时分区(一年 8760 个分区)更合理

来看一个实际例子:

-- 按日期和事件类型分区
CREATE TABLE IF NOT EXISTS user_events_partitioned (
    event_id STRING,
    user_id STRING,
    event_type STRING,
    event_time TIMESTAMP,
    page_url STRING
)
USING DELTA
PARTITIONED BY (event_date STRING, event_type STRING)
LOCATION '/data/delta/user_events_partitioned'

用 DataFrame API 写入分区表时,要这样写:

# 假设 df 已经包含 event_date 和 event_type 列
df.write \
    .format("delta") \
    .partitionBy("event_date", "event_type") \
    .mode("append") \
    .save("/data/delta/user_events_partitioned")

关键点:分区字段必须是表 schema 中已有的字段。你不能凭空指定一个不存在的列作为分区键。

存储路径的设计哲学

关于存储路径,我踩过不少坑。给你三个建议:

  • 路径要有层次:比如 /data/delta/{数据库名}/{表名},方便管理
  • 避免特殊字符:路径里不要用空格、中文、特殊符号,否则某些工具解析会出问题
  • 考虑数据生命周期:如果数据需要定期清理,分区路径设计要方便按分区删除

我曾经接手过一个项目,他们把表直接建在 HDFS 根目录下,结果半年后整个目录乱成一锅粥。你想想看,几十张表混在一起,想找一张表的数据都得靠猜。

三种创建方式的对比

特性 Spark SQL DataFrame API 分区表
易用性 高,SQL 语法直观 中,需要写代码 中,需要设计分区键
灵活性 低,结构固定 高,可动态控制 高,可按需调整
性能 一般 一般 高,分区裁剪
适用场景 数据分析师、临时查询 ETL 开发、自动化流程 大表、频繁查询的表

核心知识体系

下面这张图帮你理清本章的知识脉络:

创建 Delta 表的三种方式 Delta 表 Spark SQL 方式 DataFrame API 方式 分区表方式 CREATE TABLE ... USING DELTA 适合数据分析师、临时查询 df.write.format("delta").save() 适合 ETL 开发、自动化流程 PARTITIONED BY (date, type) 适合大表、频繁查询场景 核心要点 分区设计决定查询性能,路径规划影响运维效率

避坑指南

最后,分享几个我亲身踩过的坑:

  • 分区字段类型要一致:我曾经把日期字段设成 STRING 类型,结果按时间范围查询时没法做分区裁剪,性能一塌糊涂。建议日期字段用 DATE 或 TIMESTAMP 类型。
  • 不要过度分区:分区太多会导致小文件问题。每个分区至少要有 128MB 以上的数据量,否则查询时反而更慢。
  • 路径权限要提前配好:创建表时如果路径没有写入权限,Spark 会直接报错。建议提前创建好目录并设置好权限。