第11课:PySpark|Spark SQL核心概念、DataFrame与Dataset设计思想入门

在这里插入图片描述

文章目录


一、课前导读

在前面的课程中,我们深入学习了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正是为了解决这些问题而诞生的。它提供了一种用于结构化数据处理的编程抽象——DataFrameDataset,并内置了强大的Catalyst优化器,能够自动优化执行计划。同时,Spark SQL支持使用SQL语句直接查询,极大地降低了大数据处理的门槛。

本节课作为Spark SQL的入门课,将系统介绍Spark SQL的架构、DataFrame与Dataset的设计思想、与RDD的对比,以及它们如何利用Catalyst优化器和Tungsten引擎实现性能飞跃。学完这节课,你将理解为什么在实际开发中应优先使用DataFrame而非RDD,以及如何从RDD平滑过渡到DataFrame。

二、学习目标

完成本节课的学习后,你将能够:

  1. 理解Spark SQL的定位:知道Spark SQL在Spark生态中的角色,以及它相比RDD的优势
  2. 掌握DataFrame核心概念:明白DataFrame是带Schema的分布式数据集,类似于关系表
  3. 理解Dataset设计思想:区分强类型Dataset和弱类型DataFrame(Python中两者统一为DataFrame)
  4. 了解Catalyst优化器:知道Spark SQL如何通过逻辑优化和物理优化提升执行效率
  5. 了解Tungsten引擎:理解代码生成、内存管理等技术如何加速计算
  6. 能够创建DataFrame:从RDD、文件、Hive表等多种数据源创建DataFrame
  7. 掌握DataFrame基础操作:使用select、filter、groupBy等API进行数据处理
  8. 运行SQL查询:将DataFrame注册为临时视图,使用Spark SQL执行查询
  9. 理解RDD与DataFrame的转换:在两种API之间按需切换

三、核心理论知识点

知识点说明
Spark SQLSpark处理结构化数据的模块,包含DataFrame API、SQL接口和Catalyst优化器
DataFrame带Schema的分布式数据集,以列组织数据,类似关系数据库的表
DatasetSpark 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通过引入DataFrameCatalyst优化器解决了这些问题:

  • DataFrame为数据提供了Schema,让Spark知道数据的形状。
  • Catalyst优化器可以对逻辑计划进行重写和优化,如谓词下推、列剪枝、常量折叠等。
  • Tungsten引擎通过代码生成将表达式编译为Java字节码,实现接近手写优化的性能。

4.2 DataFrame vs RDD:一个类比的例子

想象你是一位仓库管理员。

  • RDD方式:你只知道有货物,但不知道货物是什么样子。每次你要取货物,都需要自己记住:这批货物是“箱子,里面装了什么,重量多少”。你要手动把箱子搬来搬去,自己决定如何摆放。灵活但累。

  • DataFrame方式:你有一张仓库地图,上面标注了每个区域放什么货(Schema)。你想找“所有重量>10kg的电子产品”,地图会自动告诉你最快路径,甚至可以用货物管理系统的查询语言(SQL)来问。你不必关心每个箱子的细节,系统会优化搬运路线。

简单说:RDD是“通用集装箱”,DataFrame是“标签清晰的货架”。

4.3 Catalyst优化器:智能查询优化

Catalyst优化器的工作流程分为四个阶段:

  1. 解析:将SQL字符串或DataFrame API调用解析为抽象语法树(AST),即未绑定的逻辑计划。
  2. 分析:通过Catalog(元数据仓库)解析列名、表名、函数,生成逻辑计划。
  3. 逻辑优化:应用规则优化逻辑计划,如谓词下推、列剪枝、常量折叠、投影合并等。
  4. 物理优化:根据成本模型选择最优的物理执行策略,如选择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大数据,改用takeshow或写入外部存储。

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

  • selectfilterwithColumn 等都是转换算子,返回新的DataFrame。
  • col("age") 是列表达式,可配合各种函数使用。
  • groupBy().agg() 完成分组聚合,avgsum 等是内置聚合函数。

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 写入分区表时产生大量小文件

原因:分区数过多或每个分区数据量小。

解决:写入前使用coalescerepartition减少分区数;开启spark.sql.adaptive.coalescePartitions.enabled

十一、本节课知识点总结

DataFrame vs RDD 对比

特性RDDDataFrame
数据表示任意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 ...

十二、课后思考作业

作业一:理论理解题

  1. 请解释Catalyst优化器的四个阶段,并说明每个阶段的主要作用。

  2. 为什么DataFrame在大多数场景下比RDD性能更好?请从内存管理、代码生成、优化策略三个方面阐述。

  3. 列举至少五种DataFrame支持的外部数据源格式,并说明列式存储相比行式存储的优势。

作业二:代码实践题

  1. 从给定的JSON文件(用户数据)中读取,执行以下分析:

    • 统计每个性别的平均年龄
    • 找出年龄在20-30之间的用户中,收入最高的前5名
    • 将结果保存为Parquet格式,且只保留必要列
  2. 编写一个程序,将RDD转换为DataFrame,并分别使用RDD API和DataFrame API完成相同的大数据聚合任务,比较执行时间差异,分析原因。

  3. 使用explain查看一个包含joinaggregation的DataFrame的执行计划,标注出逻辑优化和物理优化的部分。

作业三:场景应用题

某电商平台数据仓库中,订单表(orders)存储了3年的数据,每天新增500万条。经常需要执行以下查询:

  • 按天统计销售额、订单数
  • 按商品品类统计季度销售额
  • 分析新老用户(首次购买日期)的消费行为

请设计基于Spark SQL的解决方案,包括:

  1. 数据应该如何分区存储(文件格式、分区键)?
  2. 如何通过谓词下推和列剪枝提高查询效率?
  3. 写出对应上述三种查询的SQL或DataFrame代码。
  4. 如果查询响应时间要求<10秒,你有哪些优化手段?

作业四:拓展研究

  1. 阅读Spark官方文档中“Spark SQL, DataFrames and Datasets Guide”,深入了解Dataset在Scala/Java中的强类型特性,并写一篇对比文章(PySpark vs Scala)。

  2. 研究Tungsten引擎的内存编码格式(UnsafeRow),分析其如何减少内存占用和GC压力。

  3. 测试不同文件格式(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 从入门到精通》系列课程导航

去订阅

🌟 感谢您耐心阅读到这里!
💡 如果本文对您有所启发欢迎:
👍 点赞📌 收藏 📤 分享给更多需要的伙伴。
🗣️ 期待在评论区看到您的想法, 共同进步。
🔔 关注我,持续获取更多干货内容~
🤗 我们下篇文章见~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thomas.Sir

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值