3、创建 Delta 表:使用 Spark SQL 创建表、使用 DataFrame API 创建表、管理表的 Location 和 Properties
创建 Delta 表,说白了就是把数据湖里的文件,变成一张能查能改的“表”。
我刚开始接触 Delta Lake 时,觉得这不就是个 parquet 加了个壳吗?后来才发现,这个“壳”带来的 ACID 事务和 schema 管理,简直是救命稻草。今天咱们就聊聊怎么创建 Delta 表,以及怎么管好它的“家”和“脾气”。
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。
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 表有事务日志,文件路径变了,日志里记录的路径还是旧的。正确的做法是:
- 用
ALTER TABLE ... SET LOCATION更新元数据。 - 或者用
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 表的三种方式以及核心管理要素:
3.5 实战建议
最后,分享几个我在项目中总结的经验:
- 创建表时就把 Properties 配好,别等出问题了再改。比如开启 CDF,如果表已经写了很多数据,再开启可能会丢失历史变更记录。
- 分区字段要选对。按日期分区是常见做法,但如果你有大量按地区查询的场景,也可以考虑按地区分区。分区字段的基数不要太大,否则会产生大量小目录。
- Location 路径要有规范。我习惯用
/data/{项目}/{表名}的格式,这样不同项目的数据不会混在一起。 - 用 DataFrame API 创建表时,记得先定义好 schema。让 Spark 推断 schema 虽然方便,但字段类型可能不是你想要的。比如整数被推断成 long,日期被推断成 string,后面查起来很麻烦。
USING delta,结果 Spark 默认用了 parquet。后来想用 Delta 的特性,发现表不支持,只能重建。所以,建表语句一定要仔细检查。
嗯,创建 Delta 表其实不难,但细节决定成败。把 Location 和 Properties 管好了,后面的数据治理工作会顺畅很多。