SparkSQL 提供了多种灵活的方式来提取和保存数据,支持各种数据源和格式。以下是全面的操作方法:
一、数据提取(读取)
1. 基本读取方法
// 通用读取模板
val df = spark.read
.format("数据源格式") // json, csv, parquet, jdbc等
.option("选项键", "选项值") // 数据源特定选项
.load("数据路径")
2. 常见数据源读取示例
(1) JSON 文件
val jsonDF = spark.read
.json("path/to/file.json") // 简洁写法
// 或完整写法
val jsonDF = spark.read
.format("json")
.option("multiLine", true) // 处理多行JSON
.option("mode", "PERMISSIVE") // 解析模式(PERMISSIVE/DROPMALFORMED/FAILFAST)
.load("path/to/file.json")
(2) CSV 文件
val csvDF = spark.read
.option("header", "true") // 使用首行作为列名
.option("inferSchema", "true") // 自动推断列类型
.option("delimiter", ",") // 指定分隔符
.option("nullValue", "NA") // 指定空值表示
.csv("path/to/file.csv")
(3) Parquet 文件
val parquetDF = spark.read
.parquet("path/to/file.parquet") // 简洁写法
// 或指定schema
val schema = new StructType()
.add("name", StringType)
.add("age", IntegerType)
val parquetDF = spark.read
.schema(schema)
.parquet("path/to/file.parquet")
(4) JDBC 数据库
val jdbcDF = spark.read
.format("jdbc")
.option("url", "jdbc:mysql://host:port/db")
.option("dbtable", "table_name")
.option("user", "username")
.option("password", "password")
.option("fetchSize", "10000") // 每次读取行数
.load()
3. 高级读取技巧
(1) 分区发现(针对分区存储)
spark.read.parquet("path/to/partitioned_table")
.where($"year" === 2023 && $"month" === 12) // 分区裁剪
(2) 读取目录下所有文件
spark.read.json("path/to/directory/*.json")
(3) 读取压缩文件
spark.read
.option("compression", "gzip") // 支持gzip, bzip2, snappy等
.csv("path/to/file.csv.gz")
二、数据保存(写入)
1. 基本保存方法
// 通用保存模板
df.write
.format("数据格式")
.option("选项键", "选项值")
.mode("保存模式") // append/overwrite/ignore/error
.save("保存路径")
2. 常见数据源保存示例
(1) 保存为JSON
df.write
.mode("overwrite")
.json("output/path")
// 控制输出
df.write
.option("compression", "gzip")
.option("dateFormat", "yyyy-MM-dd")
.json("output/path")
(2) 保存为CSV
df.write
.option("header", "true")
.option("delimiter", "|")
.mode("append")
.csv("output/path")
(3) 保存为Parquet
df.write
.partitionBy("year", "month") // 按列分区存储
.mode("overwrite")
.parquet("output/path")
(4) 保存到JDBC数据库
df.write
.format("jdbc")
.option("url", "jdbc:mysql://host:port/db")
.option("dbtable", "new_table")
.option("user", "username")
.option("password", "password")
.option("batchsize", 10000) // 批量写入大小
.mode("append") // 表不存在会自动创建
.save()
3. 高级保存技巧
(1) 分区写入
df.write
.partitionBy("department") // 按部门分区
.parquet("output/path")
(2) 分桶写入
df.write
.bucketBy(50, "user_id") // 50个桶,按user_id分桶
.sortBy("create_time") // 桶内排序
.saveAsTable("bucketed_table") // 保存为Hive表
(3) 控制文件数量
df.coalesce(5) // 减少到5个分区
.write.parquet("output/path")
// 或
df.repartition(10, $"department") // 按部门重新分区
.write.parquet("output/path")
(4) 保存为Hive表
df.write
.saveAsTable("database.table_name") // 内部表
df.write
.option("path", "/hdfs/path")
.saveAsTable("database.external_table") // 外部表
三、特殊场景处理
1. 处理复杂数据类型
// 写入嵌套结构
df.withColumn("json_col", to_json(struct($"name", $"age")))
.write.json("output/path")
// 读取时解析
spark.read
.option("multiLine", true)
.json("path/to/complex.json")
2. 增量写入
// 使用Delta Lake格式实现ACID
df.write
.format("delta")
.mode("append")
.save("/delta/table")
// 或使用Hive分区实现增量
df.write
.partitionBy("dt")
.mode("append")
.parquet("/data/table/dt=20230101")
3. 数据验证后写入
val validatedDF = df.filter($"age".isNotNull && $"age" > 0)
validatedDF.write
.mode("overwrite")
.parquet("output/path")
四、性能优化建议
1. 读取优化:
使用`schema`选项避免推断开销
对分区数据使用分区发现
JDBC读取使用`partitionColumn`并行读取
2. 写入优化:
控制输出文件数量(`coalesce`/`repartition`)
对大表使用分区/分桶
JDBC写入使用合适的`batchsize`
3. 格式选择:
分析型查询优先选择Parquet/ORC
行级操作考虑Avro
临时数据可使用Delta Lake
4. 通用建议:
监控写入任务避免小文件问题
定期压缩小文件(`OPTIMIZE`命令)
考虑使用缓存加速重复读取