创建第一个 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 开发、自动化流程 | 大表、频繁查询的表 |
核心知识体系
下面这张图帮你理清本章的知识脉络:
避坑指南
最后,分享几个我亲身踩过的坑:
- 分区字段类型要一致:我曾经把日期字段设成 STRING 类型,结果按时间范围查询时没法做分区裁剪,性能一塌糊涂。建议日期字段用 DATE 或 TIMESTAMP 类型。
- 不要过度分区:分区太多会导致小文件问题。每个分区至少要有 128MB 以上的数据量,否则查询时反而更慢。
- 路径权限要提前配好:创建表时如果路径没有写入权限,Spark 会直接报错。建议提前创建好目录并设置好权限。