
文章目录
一、课前导读
在前面的课程中,我们深入学习了RDD——Spark Core的基石。我们知道了如何通过RDD算子进行分布式数据处理,也理解了宽窄依赖、分区、缓存、调度等底层机制。RDD强大而灵活,但同时也存在一些不足:对于结构化数据的处理,RDD API 略显繁琐;需要手动写大量样板代码;优化依赖程序员而非引擎。
例如,用RDD实现“按部门统计员工平均工资”:
rdd.map(lambda x: (x.dept, (x.salary, 1)))
.reduceByKey(lambda a,b: (a[0]+b[0], a[1]+b[1]))
.mapValues(lambda x: x[0]/x[1])
如果用SQL表达,则简洁得多:SELECT dept, AVG(salary) FROM emp GROUP BY dept。SQL不仅可读性高,而且数据库可以基于统计信息和优化规则生成高效的执行计划。
Spark SQL正是为了解决这些问题而诞生的。它提供了一种用于结构化数据处理的编程抽象——DataFrame和Dataset,并内置了强大的Catalyst优化器,能够自动优化执行计划。同时,Spark SQL支持使用SQL语句直接查询,极大地降低了大数据处理的门槛。
本节课作为Spark SQL的入门课,将系统介绍Spark SQL的架构、DataFrame与Dataset的设计思想、与RDD的对比,以及它们如何利用Catalyst优化器和Tungsten引擎实现性能飞跃。学完这节课,你将理解为什么在实际开发中应优先使用DataFrame而非RDD,以及如何从RDD平滑过渡到DataFrame。
二、学习目标
完成本节课的学习后,你将能够:
- 理解Spark SQL的定位:知道Spark SQL在Spark生态中的角色,以及它相比RDD的优势
- 掌握DataFrame核心概念:明白DataFrame是带Schema的分布式数据集,类似于关系表
- 理解Dataset设计思想:区分强类型Dataset和弱类型DataFrame(Python中两者统一为DataFrame)
- 了解Catalyst优化器:知道Spark SQL如何通过逻辑优化和物理优化提升执行效率
- 了解Tungsten引擎:理解代码生成、内存管理等技术如何加速计算
- 能够创建DataFrame:从RDD、文件、Hive表等多种数据源创建DataFrame
- 掌握DataFrame基础操作:使用select、filter、groupBy等API进行数据处理
- 运行SQL查询:将DataFrame注册为临时视图,使用Spark SQL执行查询
- 理解RDD与DataFrame的转换:在两种API之间按需切换
三、核心理论知识点
| 知识点 | 说明 |
|---|---|
| Spark SQL | Spark处理结构化数据的模块,包含DataFrame API、SQL接口和Catalyst优化器 |
| DataFrame | 带Schema的分布式数据集,以列组织数据,类似关系数据库的表 |
| Dataset | Spark 1.6引入的强类型API,在Scala/Java中提供编译时类型检查;PySpark中DataFrame即Dataset[Row] |
| Catalyst优化器 | 基于Scala函数式编程的查询优化框架,包含逻辑优化和物理优化 |
| Tungsten引擎 | Spark SQL的物理执行引擎,提供内存管理、代码生成、缓存友好计算 |
| Schema | 数据的结构信息,包括列名、数据类型和空值约束 |
| 临时视图 | DataFrame注册的临时表,可在SparkSession中使用SQL查询 |
| 外部数据源 | 支持JSON、Parquet、CSV、JDBC、Hive等多种格式和系统 |
| 谓词下推 | 将过滤条件推到数据源端执行,减少数据传输 |
| 列式存储 | 按列存储数据,利于压缩和只读所需列 |
四、原理通俗讲解
4.1 为什么需要Spark SQL?
RDD的强大在于其灵活性和对任意数据类型的支持,但处理结构化数据时存在几个痛点:
痛点一:代码冗长
即使是很简单的聚合操作,也需要写map、reduceByKey等底层操作,代码量大且容易出错。
痛点二:优化困难
RDD的执行计划完全由程序员书写的算子顺序决定,Spark很难自动优化。例如,rdd.filter(condition1).filter(condition2) 不会自动合并为 filter(condition1 && condition2);rdd.map(f1).map(f2) 不会自动合并为 map(f2(f1))。
痛点三:无法利用高级特性
无法利用统计信息和列式存储优势,每次都读取全量数据。
Spark SQL通过引入DataFrame和Catalyst优化器解决了这些问题:
- DataFrame为数据提供了Schema,让Spark知道数据的形状。
- Catalyst优化器可以对逻辑计划进行重写和优化,如谓词下推、列剪枝、常量折叠等。
- Tungsten引擎通过代码生成将表达式编译为Java字节码,实现接近手写优化的性能。
4.2 DataFrame vs RDD:一个类比的例子
想象你是一位仓库管理员。
-
RDD方式:你只知道有货物,但不知道货物是什么样子。每次你要取货物,都需要自己记住:这批货物是“箱子,里面装了什么,重量多少”。你要手动把箱子搬来搬去,自己决定如何摆放。灵活但累。
-
DataFrame方式:你有一张仓库地图,上面标注了每个区域放什么货(Schema)。你想找“所有重量>10kg的电子产品”,地图会自动告诉你最快路径,甚至可以用货物管理系统的查询语言(SQL)来问。你不必关心每个箱子的细节,系统会优化搬运路线。
简单说:RDD是“通用集装箱”,DataFrame是“标签清晰的货架”。
4.3 Catalyst优化器:智能查询优化
Catalyst优化器的工作流程分为四个阶段:
- 解析:将SQL字符串或DataFrame API调用解析为抽象语法树(AST),即未绑定的逻辑计划。
- 分析:通过Catalog(元数据仓库)解析列名、表名、函数,生成逻辑计划。
- 逻辑优化:应用规则优化逻辑计划,如谓词下推、列剪枝、常量折叠、投影合并等。
- 物理优化:根据成本模型选择最优的物理执行策略,如选择BroadcastJoin还是SortMergeJoin。
这一过程对用户透明,但能带来数十倍的性能提升。
4.4 Tungsten引擎:加速物理执行
Tungsten是Spark SQL的物理执行后端,三大技术:
- 内存管理:使用二进制内存格式(类似于Parquet的编码)存储数据,比Java对象更紧凑,减少GC压力。
- 代码生成:将表达式编译为Java字节码,避免虚函数调用和中间对象产生。例如
col("age") > 18会生成为row.getInt(3) > 18这样的直接访问代码。 - 缓存友好:数据以列式格式存储,按批次处理(一次处理4096行),提高CPU缓存命中率。
这些技术使得DataFrame在大多数场景下性能远超RDD。
4.5 Dataset:强类型DataFrame
在Scala/Java中,Dataset是强类型的,即 Dataset[Person] 表示每条数据是Person对象,编译时检查字段类型。在PySpark中,由于Python的动态特性,DataFrame就是 Dataset[Row],没有独立的强类型Dataset API。因此本节课聚焦DataFrame,但理解Dataset的设计思想有助于阅读Spark源码和Scala接口。
五、重点概念拆解
5.1 DataFrame的本质
在PySpark中,DataFrame是一个分布式行集合,每行是一个Row对象,有定义好的Schema。可以从以下角度理解:
- 逻辑视角:表,有列名和类型。
- 物理视角:底层由RDD[Row]实现,但增加了Schema信息和优化器。
创建DataFrame的常见方式:
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, IntegerType
spark = SparkSession.builder.getOrCreate()
# 1. 从列表创建
df = spark.createDataFrame([(1, "Alice"), (2, "Bob")], ["id", "name"])
# 2. 指定Schema创建
schema = StructType([StructField("id", IntegerType()), StructField("name", StringType())])
df = spark.createDataFrame([(1, "Alice"), (2, "Bob")], schema)
# 3. 从RDD创建
rdd = sc.parallelize([(1, "Alice"), (2, "Bob")])
df = rdd.toDF(["id", "name"])
5.2 DataFrame的Schema
Schema可以通过df.printSchema()查看,包含字段名、类型和是否可为空。Schema是DataFrame优化的基础,让Spark知道数据的形状,从而进行列剪枝等优化。
5.3 DataFrame API vs SQL
DataFrame提供了两种操作方式:
- DSL风格:
df.select("name").filter(df.age > 18) - SQL风格:
df.createOrReplaceTempView("people"); spark.sql("SELECT name FROM people WHERE age > 18")
两者底层都会被转化为相同的逻辑计划,性能一致,选择哪种取决于个人偏好和场景。
5.4 惰性执行与RDD一致
DataFrame的转换操作(select、filter、join等)也是惰性的,只有行动操作(show、count、collect)才触发计算。Catalyst优化也是在行动时才进行。
5.5 与Hive的集成
Spark SQL可以读取Hive表,只需要将enableHiveSupport()打开,并配置hive metastore地址。Spark SQL支持大部分Hive语法,是数据仓库迁移的重要工具。
5.6 外部数据源
DataFrame支持丰富的输入输出格式:
spark.read.json(path)spark.read.parquet(path)spark.read.csv(path).option("header", true)spark.read.format("jdbc")...等
输出同理:df.write.parquet(path)
5.7 Catalyst优化示例
原始代码:df.where("age > 18").select("name").where("name = 'Alice'")
逻辑优化后可能变为:df.select("name").where("age > 18 AND name = 'Alice'")(谓词合并)
物理优化:选择是否使用BroadcastJoin等。
六、易错点避坑
6.1 误区:DataFrame比RDD慢
实际上,由于Catalyst优化和Tungsten引擎,DataFrame通常比RDD快(尤其是处理结构化数据)。只有在需要精细控制数据分布或使用复杂自定义逻辑时,RDD才有优势。
6.2 误区:df.collect() 和 rdd.collect() 一样
两者都会将全部数据拉到Driver,但df.collect()返回的是list[Row],Row对象比普通Python元组更重。大数据量下同样会OOM。避免collect大数据,改用take、show或写入外部存储。
6.3 忽略Schema影响性能
如果从RDD创建DataFrame时不指定Schema,Spark会通过采样推断类型,产生额外开销。对大数据集,应显式提供Schema。
6.4 误用UDF导致性能下降
在DataFrame中调用Python UDF会破坏Catalyst优化,每行数据都会序列化往返Python/JVM,性能较差。应尽量使用内置函数,或使用Pandas UDF(向量化)。
6.5 在循环中频繁创建临时视图
每次createOrReplaceTempView都会在SparkSession中注册一个表,如果循环次数多,会积累大量临时表占用内存。应重用视图,或使用createOrReplaceGlobalTempView。
七、完整实战案例
本案例将综合演示DataFrame的创建、Schema定义、常用操作、SQL查询以及与RDD的互操作。
# ============== spark_sql_intro_demo.py ==============
# 功能:Spark SQL与DataFrame入门实战,演示创建、操作、优化、互转
# 环境:PySpark 3.x,Python 3.8+
from pyspark.sql import SparkSession
from pyspark.sql.types import StructType, StructField, StringType, IntegerType, DoubleType
from pyspark.sql.functions import col, avg, sum as spark_sum, count, when
from pyspark.sql import Row
import time
# ========== 1. 创建SparkSession ==========
spark = SparkSession.builder \
.appName("SparkSQLIntro") \
.master("local[4]") \
.config("spark.sql.shuffle.partitions", "4") \
.config("spark.sql.adaptive.enabled", "true") \
.getOrCreate()
sc = spark.sparkContext
sc.setLogLevel("WARN")
print("=" * 80)
print("Spark SQL 与 DataFrame 入门实战")
print("=" * 80)
# ========== 2. 创建DataFrame的多种方式 ==========
print("\n步骤1: 创建DataFrame")
print("-" * 60)
# 2.1 从列表创建(自动推断Schema)
data = [("Alice", 34, "F", 5000),
("Bob", 45, "M", 6000),
("Cathy", 29, "F", 5500),
("David", 38, "M", 4500),
("Ella", 52, "F", 7000),
("Frank", 41, "M", 6200)]
columns = ["name", "age", "gender", "salary"]
df1 = spark.createDataFrame(data, columns)
print("方式1: 从列表+列名创建")
df1.printSchema()
df1.show()
# 2.2 指定Schema创建(避免类型推断开销)
schema = StructType([
StructField("name", StringType(), True),
StructField("age", IntegerType(), True),
StructField("gender", StringType(), True),
StructField("salary", DoubleType(), True)
])
df2 = spark.createDataFrame(data, schema)
print("方式2: 显式指定Schema")
df2.printSchema()
# 2.3 从RDD[Row]创建
rdd = sc.parallelize(data).map(lambda x: Row(name=x[0], age=x[1], gender=x[2], salary=x[3]))
df3 = spark.createDataFrame(rdd, schema)
print("方式3: 从RDD[Row]创建")
df3.show(2)
# 2.4 从RDD[tuple] + toDF方法
rdd2 = sc.parallelize(data)
df4 = rdd2.toDF(columns)
print("方式4: RDD.toDF")
df4.show(2)
# ========== 3. DataFrame基础操作(DSL风格) ==========
print("\n" + "=" * 80)
print("步骤2: DataFrame常用转换算子")
print("=" * 80)
# 使用df1进行演示
df = df1
# select: 选择列
print("select: 选择特定列")
df.select("name", "salary").show(3)
# filter / where: 条件过滤
print("filter: 年龄大于40")
df.filter(col("age") > 40).show()
# withColumn: 添加或替换列
print("withColumn: 添加税后工资列(假设税率10%)")
df_with_tax = df.withColumn("after_tax", col("salary") * 0.9)
df_with_tax.show()
# groupBy + agg: 聚合
print("groupBy: 按性别统计平均年龄和总薪资")
df.groupBy("gender").agg(
avg("age").alias("avg_age"),
spark_sum("salary").alias("total_salary")
).show()
# orderBy: 排序
print("orderBy: 按薪资降序")
df.orderBy(col("salary").desc()).show()
# drop: 删除列
df_dropped = df.drop("gender")
print("drop后列:", df_dropped.columns)
# ========== 4. SQL风格查询 ==========
print("\n" + "=" * 80)
print("步骤3: 使用SQL查询DataFrame")
print("=" * 80)
# 注册临时视图
df.createOrReplaceTempView("employees")
# 执行SQL
result_sql = spark.sql("""
SELECT gender,
COUNT(*) as count,
AVG(age) as avg_age,
SUM(salary) as total_salary
FROM employees
WHERE salary > 5000
GROUP BY gender
ORDER BY total_salary DESC
""")
print("SQL查询结果:")
result_sql.show()
# ========== 5. DataFrame与RDD互转 ==========
print("\n" + "=" * 80)
print("步骤4: DataFrame <-> RDD 转换")
print("=" * 80)
# DataFrame转RDD[Row]
rdd_from_df = df.rdd
print(f"DataFrame转换为RDD,分区数: {rdd_from_df.getNumPartitions()}")
print("RDD样例:", rdd_from_df.take(2))
# RDD[Row]转回DataFrame,需要重新推断Schema或提供Schema
new_df = spark.createDataFrame(rdd_from_df)
print("RDD转回DataFrame,Schema:", new_df.schema)
# 注意:如果RDD不是Row类型,需要映射
rdd_tuple = sc.parallelize([(1, "x"), (2, "y")])
df_from_tuple = rdd_tuple.toDF(["id", "value"])
df_from_tuple.show()
# ========== 6. 处理不同数据源 ==========
print("\n" + "=" * 80)
print("步骤5: 读写外部数据源")
print("=" * 80)
# 写入JSON(本地测试)
df.write.mode("overwrite").json("/tmp/employees_json")
print("已写入JSON到 /tmp/employees_json")
# 读取JSON
df_json = spark.read.json("/tmp/employees_json")
print("读取JSON文件:")
df_json.show(2)
# 写入Parquet(列式存储,推荐)
df.write.mode("overwrite").parquet("/tmp/employees_parquet")
df_parquet = spark.read.parquet("/tmp/employees_parquet")
print("Parquet文件Schema:")
df_parquet.printSchema()
# ========== 7. 性能对比:RDD vs DataFrame ==========
print("\n" + "=" * 80)
print("步骤6: 性能对比(RDD vs DataFrame)")
print("=" * 80)
# 生成大规模数据
large_data = [(i, f"user_{i}", i % 10) for i in range(1_000_000)]
large_rdd = sc.parallelize(large_data, numSlices=8)
print(f"生成100万条数据,RDD分区数: {large_rdd.getNumPartitions()}")
# RDD实现: 计算每个分组的平均value
def rdd_avg():
start = time.time()
result = large_rdd.map(lambda x: (x[2], (x[0], 1))) \
.reduceByKey(lambda a,b: (a[0]+b[0], a[1]+b[1])) \
.mapValues(lambda v: v[0]/v[1]) \
.collect()
elapsed = time.time() - start
print(f"RDD耗时: {elapsed:.2f} 秒")
return elapsed
# DataFrame实现
def df_avg():
start = time.time()
df = large_rdd.toDF(["value", "name", "group"])
result = df.groupBy("group").avg("value").collect()
elapsed = time.time() - start
print(f"DataFrame耗时: {elapsed:.2f} 秒")
return elapsed
print("执行性能测试...")
rdd_time = rdd_avg()
df_time = df_avg()
print(f"DataFrame比RDD快 {rdd_time/df_time:.1f} 倍")
# ========== 8. 查看物理执行计划 ==========
print("\n" + "=" * 80)
print("步骤7: 查看Catalyst优化后的执行计划")
print("=" * 80)
df_sales = spark.createDataFrame([(1, 100), (2, 200), (3, 150)], ["id", "amount"])
# 一个简单查询
query = df_sales.select("id", "amount").filter(col("amount") > 120).select("id")
print("DataFrame转换序列:")
query.show()
print("优化后的物理计划:")
query.explain(mode="extended") # 显示解析、优化、物理计划
# ========== 9. 使用内置函数避免UDF ==========
print("\n" + "=" * 80)
print("步骤8: 使用内置函数进行复杂计算")
print("=" * 80)
from pyspark.sql.functions import udf, when, round, concat, lit
# 不推荐:Python UDF(每行序列化)
@udf(returnType=DoubleType())
def compute_bonus(salary, gender):
if gender == "F":
return salary * 0.2
else:
return salary * 0.1
df_bonus_udf = df.withColumn("bonus", compute_bonus(col("salary"), col("gender")))
print("使用UDF添加奖金列:")
df_bonus_udf.select("name", "gender", "salary", "bonus").show()
# 推荐:使用内置函数(更高效)
df_bonus_builtin = df.withColumn("bonus", when(col("gender") == "F", col("salary") * 0.2).otherwise(col("salary") * 0.1))
print("使用内置when函数:")
df_bonus_builtin.select("name", "gender", "salary", "bonus").show()
# ========== 10. 清理 ==========
print("\n清理临时文件...")
import shutil
shutil.rmtree("/tmp/employees_json", ignore_errors=True)
shutil.rmtree("/tmp/employees_parquet", ignore_errors=True)
spark.stop()
print("\n✅ Spark SQL入门演示完成")
八、代码逐行解析
8.1 创建DataFrame的多种方式
createDataFrame是最通用的方法,可以接受列表、RDD等。- 显式指定Schema可以避免Spark通过采样推断类型,节省时间且更精确。
toDF是RDD的隐式方法(需要from pyspark.sql import Row),方便快速转换。
8.2 DataFrame API
select、filter、withColumn等都是转换算子,返回新的DataFrame。col("age")是列表达式,可配合各种函数使用。groupBy().agg()完成分组聚合,avg、sum等是内置聚合函数。
8.3 SQL风格
createOrReplaceTempView注册临时表,仅在当前SparkSession可见。spark.sql执行SQL,返回新的DataFrame。
8.4 性能对比
RDD手动实现分组聚合需要三步:map、reduceByKey、mapValues。DataFrame只需groupBy("group").avg("value"),且Catalyst优化器会生成比手写RDD更高效的执行计划。
8.5 执行计划
explain(mode="extended") 显示四个阶段:
- Parsed Logical Plan:解析后的逻辑计划
- Analyzed Logical Plan:分析后的逻辑计划(已解析列和表)
- Optimized Logical Plan:优化后的逻辑计划
- Physical Plan:物理执行计划(如何实际运行)
九、业务场景落地应用
9.1 场景一:日志分析中的DataFrame使用
需求:分析Web日志,按URL统计PV和UV。
log_df = spark.read.text("/logs/access.log")
# 使用正则解析
log_df.select(regexp_extract("value", "(\S+)", 1).alias("ip"),
regexp_extract("value", "GET (\S+)", 1).alias("url")).createOrReplaceTempView("logs")
spark.sql("""
SELECT url, COUNT(*) as pv, COUNT(DISTINCT ip) as uv
FROM logs
GROUP BY url
ORDER BY pv DESC
""").show()
9.2 场景二:数据仓库ETL
需求:从MySQL抽取订单表,清洗后写入Hive分区表。
orders_df = spark.read.format("jdbc").options(
url="jdbc:mysql://db:3306/shop",
dbtable="orders",
user="user",
password="pass"
).load()
cleaned = orders_df.filter("amount > 0").withColumn("date", to_date("create_time"))
cleaned.write.partitionBy("date").mode("append").format("parquet").saveAsTable("ods.orders")
9.3 场景三:机器学习特征工程
DataFrame配合MLlib进行特征转换,如独热编码、向量组装。
from pyspark.ml.feature import StringIndexer, OneHotEncoder, VectorAssembler
# 省略具体转换代码
9.4 场景四:与RDD混合使用
当DataFrame API无法满足复杂逻辑时,可转为RDD处理,再转回DataFrame。
# 复杂数据清洗,例如每行需要调用外部服务
def complex_process(row):
# 一些无法用内置函数表达的逻辑
return processed_row
df_rdd = df.rdd.map(complex_process)
result_df = spark.createDataFrame(df_rdd, schema)
十、常见报错排查
10.1 AnalysisException: Table or view not found
原因:临时视图不存在,或使用了createTempView但查询时未加库名。
解决:使用createOrReplaceTempView,查询时直接使用视图名;对于全局视图使用global_temp.view_name。
10.2 Column is not iterable 类型错误
原因:混淆了DataFrame API和SQL表达式,如df.filter("age > 18" and "salary > 5000")。
解决:使用df.filter((col("age") > 18) & (col("salary") > 5000))或SQL字符串"age > 18 AND salary > 5000"。
10.3 SparkException: Cannot find a public constructor for UDF
原因:UDF函数中使用了Python类实例,不可序列化。
解决:确保UDF函数是顶层函数或staticmethod,避免在函数内引用外部不可序列化变量。
10.4 读取CSV时数据错位
原因:未指定分隔符、header、或转义字符。
解决:使用.option("delimiter", ",").option("header", "true").option("escape", "\"")等精确控制。
10.5 写入分区表时产生大量小文件
原因:分区数过多或每个分区数据量小。
解决:写入前使用coalesce或repartition减少分区数;开启spark.sql.adaptive.coalescePartitions.enabled。
十一、本节课知识点总结
DataFrame vs RDD 对比
| 特性 | RDD | DataFrame |
|---|---|---|
| 数据表示 | 任意Java/Python对象 | 带Schema的行 |
| 优化 | 无自动优化 | Catalyst优化器 |
| 性能 | 一般 | 高(Tungsten) |
| API易用性 | 底层,灵活 | 高层,简洁 |
| 适用场景 | 非结构化、复杂ETL | 结构化分析、SQL |
| Python性能 | 高(直接操作对象) | 可能受序列化影响但总体快 |
Spark SQL 架构层次
SQL / DataFrame API
↓
Unresolved Logical Plan
↓ (Catalog)
Analyzed Logical Plan
↓ (Optimizer)
Optimized Logical Plan
↓ (Planner)
Physical Plan
↓ (Tungsten)
Execution
常用DataFrame操作速查
| 操作 | DSL示例 | SQL示例 |
|---|---|---|
| 选择列 | df.select("col1", "col2") | SELECT col1, col2 FROM table |
| 过滤 | df.filter(col("age")>18) | WHERE age > 18 |
| 聚合 | df.groupBy("gender").avg("salary") | SELECT gender, AVG(salary) FROM table GROUP BY gender |
| 排序 | df.orderBy("age", ascending=False) | ORDER BY age DESC |
| 连接 | df1.join(df2, on="id", how="inner") | JOIN ... ON ... |
十二、课后思考作业
作业一:理论理解题
-
请解释Catalyst优化器的四个阶段,并说明每个阶段的主要作用。
-
为什么DataFrame在大多数场景下比RDD性能更好?请从内存管理、代码生成、优化策略三个方面阐述。
-
列举至少五种DataFrame支持的外部数据源格式,并说明列式存储相比行式存储的优势。
作业二:代码实践题
-
从给定的JSON文件(用户数据)中读取,执行以下分析:
- 统计每个性别的平均年龄
- 找出年龄在20-30之间的用户中,收入最高的前5名
- 将结果保存为Parquet格式,且只保留必要列
-
编写一个程序,将RDD转换为DataFrame,并分别使用RDD API和DataFrame API完成相同的大数据聚合任务,比较执行时间差异,分析原因。
-
使用
explain查看一个包含join和aggregation的DataFrame的执行计划,标注出逻辑优化和物理优化的部分。
作业三:场景应用题
某电商平台数据仓库中,订单表(orders)存储了3年的数据,每天新增500万条。经常需要执行以下查询:
- 按天统计销售额、订单数
- 按商品品类统计季度销售额
- 分析新老用户(首次购买日期)的消费行为
请设计基于Spark SQL的解决方案,包括:
- 数据应该如何分区存储(文件格式、分区键)?
- 如何通过谓词下推和列剪枝提高查询效率?
- 写出对应上述三种查询的SQL或DataFrame代码。
- 如果查询响应时间要求<10秒,你有哪些优化手段?
作业四:拓展研究
-
阅读Spark官方文档中“Spark SQL, DataFrames and Datasets Guide”,深入了解
Dataset在Scala/Java中的强类型特性,并写一篇对比文章(PySpark vs Scala)。 -
研究Tungsten引擎的内存编码格式(UnsafeRow),分析其如何减少内存占用和GC压力。
-
测试不同文件格式(JSON、CSV、Parquet、ORC)的读写性能,以及压缩方式的影响,给出选型建议。
提交方式:本次作业要求提交代码和运行结果截图,以及理论题的解答。鼓励使用DataFrame API完成实际数据分析任务。
扩展阅读:
- Spark官方文档: Spark SQL Guide
- 《Spark: The Definitive Guide》第5-7章
- 论文: Spark SQL: Relational Data Processing in Spark
通过本节课的学习,你已经迈入了Spark SQL的大门,理解了DataFrame的设计思想以及它相比RDD的优势。下一节课我们将深入学习DataFrame的各种创建方式和基础操作实战,让你能够熟练运用DataFrame处理复杂的数据分析任务。我们下节课见!
🔗《20节课 PySpark 从入门到精通》系列课程导航
🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

286

被折叠的 条评论
为什么被折叠?



