- 简单问问项目 -— 略
- Spark的执行流程
- 宽窄依赖
- RDD和Dateframe
- 排序窗口函数
- python的特点
- ·is 和 ==的区别
- sq!:跑得慢,哪里可能有问题
2. Spark 的执行流程
考察知识点
Spark 的核心组件(Driver/Executor)分工、DAG(有向无环图)构建逻辑、Stage 划分依据(宽窄依赖)、Task 调度与执行机制、结果返回流程
参考回答
Spark 执行流程围绕 “Driver 统筹调度 + Executor 并行计算” 展开,从任务提交到结果输出需经历 6 个核心阶段,以 Spark Submit 提交 WordCount 任务为例(输入数据为 2 个文件,共 100MB):
(1)任务提交与初始化(Driver 启动)
- 核心动作:用户通过
spark-submit
脚本提交任务(如spark-submit --class org.apache.spark.examples.WordCount wordcount.jar
),集群资源管理器(YARN/Mesos/Standalone)为任务分配资源,启动 Driver 进程。 - Driver 职责:
- 初始化 SparkContext(Spark 核心入口,负责与资源管理器通信、创建 RDD、调度任务);
- 解析用户代码,生成逻辑执行计划(如 WordCount 的 “读取文件→切分单词→计数→汇总” 步骤);
- 向资源管理器申请 Executor 资源(如申请 2 个 Executor,每个分配 2 核 4GB 内存)。
- 示例:Driver 启动后,通过
sc.textFile("hdfs://input/word.txt")
创建初始 RDD,记录数据来源(HDFS 路径)和分区数(默认按 HDFS 块大小 128MB 划分,100MB 数据分 1 个分区)。
(2)构建 DAG(有向无环图)
- 核心动作:Driver 根据 RDD 的依赖关系(宽窄依赖),构建逻辑 DAG,描述任务的计算步骤和数据流向。
- DAG 结构:以 WordCount 为例,DAG 节点为 RDD 操作,边为依赖关系:plaintext
HadoopRDD(读取文件)→ MapPartitionsRDD(flatMap切分单词)→ PairRDD(mapToPair生成<word,1>)→ ShuffledRDD(reduceByKey汇总计数)→ ResultRDD(输出结果)
- 关键逻辑:每个 RDD 操作会生成新 RDD,DAG 记录 “父 RDD→子 RDD” 的依赖关系,无循环(避免重复计算),为后续 Stage 划分提供依据。
(3)划分 Stage(任务阶段)
- 核心动作:Driver 根据 RDD 的宽窄依赖将 DAG 拆分为多个 Stage(阶段),Stage 内的 Task 可并行执行,Stage 间按依赖顺序执行(前一个 Stage 完成后,后一个 Stage 才能启动)。
- 划分规则:
- 窄依赖(Narrow Dependency):子 RDD 的每个分区仅依赖父 RDD 的一个分区(如
map
/flatMap
/filter
),可在同一 Stage 内流水线执行,无需 Shuffle; - 宽依赖(Wide Dependency):子 RDD 的一个分区依赖父 RDD 的多个分区(如
reduceByKey
/join
),需触发 Shuffle(数据跨分区重分布),以此为边界拆分 Stage。
- 窄依赖(Narrow Dependency):子 RDD 的每个分区仅依赖父 RDD 的一个分区(如
- WordCount 的 Stage 划分:
- Stage 1(Shuffle 前):包含 “读取文件→切分单词→生成 < word,1>”(均为窄依赖),输出中间结果到本地磁盘;
- Stage 2(Shuffle 后):包含 “reduceByKey 汇总计数→输出结果”(
reduceByKey
为宽依赖,触发 Shuffle 后执行汇总)。
(4)生成 Task 并调度(Executor 执行准备)
- 核心动作:
- Driver 将每个 Stage 拆分为多个Task(任务),Task 数量 = RDD 分区数(如 Stage 1 的 RDD 分区数为 1,生成 1 个 Task;Stage 2 的 ShuffledRDD 分区数默认 200,生成 200 个 Task);
- Driver 通过 “任务调度器(TaskScheduler)” 将 Task 分配给 Executor(遵循 “数据本地化” 原则,优先将 Task 分配到数据所在节点,减少网络 IO);
- Executor 启动后注册到 Driver,保持心跳通信,接收并执行 Task。
- 数据本地化优先级:
- 进程本地化(Task 与数据在同一 JVM)> 节点本地化(同一节点不同 JVM)> 机架本地化(同一机架不同节点)> 跨机架(不同机架),优先级越低,网络传输开销越大。
(5)Task 执行与 Shuffle(并行计算)
- Stage 1 执行:
- Executor 接收 Task 后,读取 HDFS 文件(数据本地化),执行 “切分单词→生成 < word,1>” 操作,将中间结果(如 < hello,1>、<world,1>)按
reduceByKey
的 Key 哈希值分区,写入本地磁盘(Shuffle Write); - 每个 Task 执行完成后,向 Driver 汇报进度(成功 / 失败),失败则重试(默认重试 4 次)。
- Executor 接收 Task 后,读取 HDFS 文件(数据本地化),执行 “切分单词→生成 < word,1>” 操作,将中间结果(如 < hello,1>、<world,1>)按
- Shuffle 过程:
- Stage 1 完成后,Stage 2 的 Task(Reducer Task)通过 “Shuffle Read” 从 Stage 1 的 Executor 本地磁盘拉取对应分区的中间结果(如 Key 哈希值为 0 的 Task 拉取所有 Key 哈希为 0 的 < word,1>);
- 拉取后对数据排序、合并(如将多个 <hello,1> 合并为 < hello,3>),再执行
reduceByKey
汇总计数。
(6)结果汇总与任务结束
- 核心动作:
- Stage 2 的 Task 执行完成后,将最终结果(如 <hello,100>、<world,80>)写入目标存储(HDFS/MySQL),或返回给 Driver(如
collect
操作); - Driver 等待所有 Stage 完成,关闭 SparkContext,释放 Executor 资源,任务结束;
- 若任务失败(如多次重试仍失败),Driver 向用户返回错误日志(如数据倾斜、资源不足)。
- Stage 2 的 Task 执行完成后,将最终结果(如 <hello,100>、<world,80>)写入目标存储(HDFS/MySQL),或返回给 Driver(如
补充注意要点
- 资源配置影响:Executor 数量、核数、内存不足会导致 Task 执行缓慢(如 2 核 Executor 跑 200 个 Task,会排队等待),需根据数据量调整(如 100GB 数据建议配置 10 个 Executor,每个 4 核 8GB);
- Shuffle 优化关键:Stage 划分的核心是宽依赖,减少宽依赖数量(如用
broadcast join
替代普通 join)可减少 Stage 数,降低 Shuffle 开销; - 实时任务差异:Spark Streaming/Flink 的执行流程类似,但会将流数据拆分为微批(Spark Streaming)或持续流(Flink),Stage/Task 会循环执行,而非一次性结束。
3. 宽窄依赖
考察知识点
宽窄依赖的定义与判断标准、两者对 Stage 划分的影响、执行效率差异、典型操作示例
参考回答
宽窄依赖是 Spark 中描述 RDD 间数据依赖关系的核心概念,直接决定 Stage 划分和任务执行效率,两者的核心差异在于 “子 RDD 分区对父 RDD 分区的依赖范围”。
(1)核心定义与判断标准
维度 | 窄依赖(Narrow Dependency) | 宽依赖(Wide Dependency) |
---|---|---|
依赖范围 | 子 RDD 的每个分区仅依赖父 RDD 的一个分区(一对一或多对一) | 子 RDD 的一个分区依赖父 RDD 的多个分区(一对多) |
数据流向 | 父 RDD 分区→子 RDD 分区为 “线性流向”,无数据交叉 | 父 RDD 多个分区→子 RDD 一个分区,存在 “数据汇聚” |
Shuffle 触发 | 不触发 Shuffle(数据无需跨分区重分布) | 触发 Shuffle(需跨分区拉取数据) |
判断关键 | 子 RDD 分区能否通过父 RDD 的 “局部数据” 计算得出 | 子 RDD 分区需聚合父 RDD 的 “多个分区数据” 才能计算得出 |
(2)典型操作示例
① 窄依赖操作(无 Shuffle)
- 一对一依赖:子 RDD 分区与父 RDD 分区一一对应,如
map
/flatMap
/filter
/mapToPair
:- 示例:
rdd.map(x => x*2)
,父 RDD 分区 1 的所有数据仅生成子 RDD 分区 1 的数 - 据,无交叉; - 特点:可在同一 Stage 内 “流水线执行”(如
rdd.flatMap(...).mapToPair(...)
可连续执行,无需落地中间结果)。
- 示例:
- 多对一依赖:多个父 RDD 分区对应一个子 RDD 分区,但每个子 RDD 分区仅依赖父 RDD 的特定分区,如
union
/cartesian
(笛卡尔积的特殊情况):- 示例:
rdd1.union(rdd2)
,若 rdd1 有 2 个分区、rdd2 有 3 个分区,子 RDD 的 5 个分区分别对应 rdd1 的 2 个分区和 rdd2 的 3 个分区,每个子 RDD 分区仅依赖一个父分区; - 注意:
cartesian
(笛卡尔积)若父 RDD 各有 2 个分区,子 RDD 有 4 个分区,每个子 RDD 分区依赖父 RDD 的一个分区组合(如子分区 (0,0) 依赖父 1 分区 0 和父 2 分区 0),仍属于窄依赖(无 Shuffle)。
- 示例:
② 宽依赖操作(触发 Shuffle)
- 聚合类操作:
groupByKey
/reduceByKey
/aggregateByKey
/combineByKey
:- 示例:
rdd.reduceByKey(_+_)
,父 RDD 的多个分区中相同 Key 的数据需汇聚到子 RDD 的同一分区(如 Key 为 “hello” 的数据可能分布在父 RDD 的 3 个分区,需汇总到子 RDD 的 1 个分区),触发 Shuffle。
- 示例:
- 关联类操作:
join
/cogroup
/leftOuterJoin
:- 示例:
rdd1.join(rdd2)
(rdd1 为 <k,v>,rdd2 为 < k,w>),子 RDD 的一个分区需依赖 rdd1 和 rdd2 中所有 Key 为该分区对应的 K 的分区(如子分区 0 依赖 rdd1 分区 0、2 和 rdd2 分区 1、3 中 Key 哈希为 0 的数据),触发 Shuffle。
- 示例:
- 重分区操作:
repartition
/sortByKey
:- 示例:
rdd.repartition(10)
,将原 RDD 的 5 个分区重分为 10 个分区,每个新分区依赖原 RDD 的多个分区(如新分区 0 依赖原分区 0 和 1 的部分数据),触发 Shuffle; - 注意:
coalesce
(减少分区)若不开启shuffle=true
(默认 false),属于窄依赖(如 5 个分区合并为 2 个,新分区 0 依赖原分区 0-2,新分区 1 依赖原分区 3-4),不触发 Shuffle。
- 示例:
(3)对 Stage 划分的影响
- Stage 划分逻辑:Spark 以 “宽依赖” 为 Stage 边界,将 DAG 拆分为多个 Stage,每个 Stage 包含连续的窄依赖操作:
- 从初始 RDD 开始,沿窄依赖向下遍历,遇到第一个宽依赖时,当前遍历的操作组成一个 Stage;
- 宽依赖之后的操作组成下一个 Stage,以此类推,直到最终 RDD。
(4)执行效率差异
- 窄依赖优势:
- 无 Shuffle 开销:数据无需跨节点传输,仅在本地执行,IO 和网络成本低;
- 流水线执行:多个窄依赖操作可连续执行(如
map→filter→flatMap
),中间结果无需落地磁盘,直接在内存传递,速度快; - 容错成本低:若某 Task 失败,仅需重新计算其依赖的父 RDD 分区(窄依赖仅一个父分区),无需重新计算整个父 RDD。
- 宽依赖劣势:
- Shuffle 开销大:数据需跨节点传输(网络 IO)、写入 / 读取磁盘(磁盘 IO),占任务总耗时的 60%-80%;
- 执行顺序受限:宽依赖对应的 Stage 需等待前一个 Stage 完全完成才能启动,无法并行;
- 容错成本高:若某 Reducer Task 失败,需重新拉取前一个 Stage 的多个父分区数据,重新计算,耗时较长。
补充注意要点
- 判断误区:并非 “多对一” 就是宽依赖,关键看 “子分区是否依赖父分区的多个分区”—— 如
union
是多对一(多个父分区对应多个子分区,每个子分区仅依赖一个父分区),属于窄依赖;而groupByKey
是一对多(一个子分区依赖多个父分区),属于宽依赖; - 优化方向:减少宽依赖数量是 Spark 优化的核心 —— 如用
broadcast join
(广播小表,避免 Shuffle)替代普通join
,将宽依赖转为窄依赖;用reduceByKey
(Map 端预聚合)替代groupByKey
(无预聚合),减少 Shuffle 数据量; - Spark SQL 关联:Spark SQL 的
join
操作会自动判断表大小,若小表 < 10MB(默认spark.sql.autoBroadcastJoinThreshold=10MB
),自动转为broadcast join
(窄依赖),否则为普通 join(宽依赖)。
4. RDD 和 DataFrame
考察知识点
RDD 与 DataFrame 的核心数据结构、API 特性、优化机制(序列化 / 执行计划)、适用场景差异、相互转换方式
参考回答
RDD(Resilient Distributed Dataset)是 Spark 的基础分布式数据结构,DataFrame 是在 RDD 基础上封装的 “带 schema 的分布式数据表”,两者在数据组织、优化能力、使用场景上差异显著。
(1)核心定义与数据结构
- RDD:
- 定义:不可变、可分区、支持并行操作的分布式数据集合,本质是 “对象的集合”(如
RDD[String]
存储字符串对象,RDD[(String, Int)]
存储键值对对象); - 数据结构:无固定 schema(结构松散),每个元素是独立的 Java/Scala 对象,Spark 仅知道元素的类型(如 String、Int),不了解元素内部的字段结构(如一个 String 是 “name,age” 格式,Spark 无法识别 “name” 和 “age” 字段);
- 示例:
val rdd = sc.textFile("user.txt")
,rdd 的每个元素是一行字符串(如 “张三,25”),Spark 无法直接操作 “姓名” 或 “年龄” 字段,需手动切分(rdd.map(line => line.split(",")(0))
)。
- 定义:不可变、可分区、支持并行操作的分布式数据集合,本质是 “对象的集合”(如
- DataFrame:
- 定义:带 schema(字段名、字段类型)的分布式数据表,本质是 “Row 对象的 RDD”(
RDD[Row]
),同时包含描述数据结构的 Metadata(schema); - 数据结构:有固定 schema(类似关系型数据库表结构),Spark 明确知道每个字段的名称和类型(如 “name: String, age: Int”),可直接按字段名操作数据;
- 示例:df 的 schema 为
root |-- name: string (nullable = true) |-- age: integer (nullable = true)
,可直接用df.select("name").filter(col("age")>20)
操作字段。scalaval df = spark.read .option("header", "true") // 第一行为字段名 .csv("user.csv") // 读取CSV文件 .withColumn("age", col("age").cast(IntegerType)) // 指定age为Int类型
- 定义:带 schema(字段名、字段类型)的分布式数据表,本质是 “Row 对象的 RDD”(
(2)API 特性差异
维度 | RDD | DataFrame |
---|---|---|
API 类型 | 命令式 API(Imperative):需手动定义 “怎么做”(如map /filter 的具体逻辑) |
声明式 API(Declarative):只需定义 “做什么”(如select("name") ,无需关心底层执行逻辑) |
字段操作 | 无字段概念,需手动解析数据(如切分字符串、提取键值对) | 支持按字段名操作(select /filter /groupBy ),无需手动解析,代码更简洁 |
类型安全 | 编译时类型安全(如RDD[Int] 传入 String 会编译报错) |
运行时类型安全(如df.select("age").cast(StringType) ,若 age 是 Int 类型,运行时才报错) |
支持语言 | 支持 Scala/Java/Python/R | 支持 Scala/Java/Python/R,且 API 风格统一(如 Python 和 Scala 的select 语法一致) |
复杂操作支持 | 支持复杂数据类型(如嵌套对象、集合),但需手动处理 | 对复杂类型(如 Array、Map)支持较弱,需用explode 等函数拆解,更适合结构化数据 |
(3)优化机制差异(核心优势)
DataFrame 比 RDD 性能提升 3-10 倍,核心源于两大优化机制:Catalyst 优化器和Tungsten 序列化,而 RDD 缺乏这些优化。
① Catalyst 优化器(执行计划优化)
- RDD 的局限:RDD 的执行计划由用户代码直接决定,Spark 无法优化 —— 如用户写
rdd.filter(_._2>10).map(_._1)
,Spark 会严格按 “过滤→映射” 执行,不会调整顺序;若用户写反顺序(map→filter
),Spark 也会执行,导致多余计算(先映射所有数据,再过滤)。 - DataFrame 的优化:Catalyst 优化器会对 SQL/DSL 代码进行 “逻辑计划→优化逻辑计划→物理计划” 的三层优化,自动选择最优执行路径:
- 逻辑计划:解析用户代码生成未优化的逻辑计划(如
df.filter(col("age")>20).select("name")
的逻辑计划是 “读取数据→过滤 age>20→选择 name”); - 优化逻辑计划:Catalyst 自动优化,如 “谓词下推”(将过滤条件
age>20
下推到数据读取阶段,提前过滤数据,减少后续处理的数据量)、“列裁剪”(仅读取name
和age
字段,而非全表字段); - 物理计划:将优化后的逻辑计划转为多个物理计划(如 “全表扫描” 或 “索引扫描”),通过成本模型(如数据量、IO 成本)选择最优计划(如若 age 有索引,选择索引扫描;否则全表扫描)。
- 逻辑计划:解析用户代码生成未优化的逻辑计划(如
- 示例:若用户写
df.select("name").filter(col("age")>20)
,Catalyst 会自动调整为 “先过滤 age>20,再选择 name”,并仅读取name
和age
字段,比 RDD 的手动操作减少 50%+ 数据量。
② Tungsten 序列化(内存与 CPU 优化)
- RDD 的序列化问题:RDD 存储的是 Java/Scala 对象,序列化时需保存对象的完整信息(如类名、字段类型、继承关系),序列化后数据量大,且反序列化时需创建大量对象,CPU 开销高;
- 示例:
RDD[(String, Int)]
存储 “张三,25”,序列化后需保存 String 和 Int 的对象头、字段值,约 50 字节 / 条;反序列化时需创建 Tuple2 对象,CPU 耗时。
- 示例:
- DataFrame 的 Tungsten 优化:
- 二进制存储:DataFrame 的 Row 对象采用 Tungsten 二进制格式存储,直接按字段类型(如 String 用 UTF-8 编码、Int 用 4 字节整数)存储原始数据,无对象头和额外信息,序列化后数据量比 RDD 减少 30%-70%(如 “张三,25” 仅需 10 字节 / 条);
- 堆外内存:支持将数据存储在 JVM 堆外内存(避免 JVM GC 对内存的回收开销),适合大数据量场景(如 100GB 数据存储在堆外,GC 时间从分钟级降至秒级);
- 向量化执行:按 “批次” 处理数据(如一次处理 1000 条 Row),而非单条处理,减少 CPU 指令跳转和函数调用开销,CPU 利用率提升 2-3 倍。
(4)适用场景差异
- RDD 适用场景:
- 非结构化 / 半结构化数据处理(如日志文件、JSON 字符串,需手动解析字段);
- 复杂数据类型操作(如嵌套对象、集合,需自定义
map
/flatMap
逻辑); - 底层 API 开发(如自定义 RDD、实现特殊的并行计算逻辑);
- 示例:处理 APP 埋点日志(每行是 JSON 字符串,需解析 “user_id”“action_type” 等字段),用 RDD 的
map(line => JSON.parse(line))
手动解析更灵活。 - DataFrame 适用场景:
- 结构化数据处理(如 CSV/Parquet 文件、数据库表,有明确字段结构);
- SQL 风格操作(如
groupBy
/join
/window
函数,适合数据分析师使用); - 高性能要求场景(如大数据量聚合、关联,需 Catalyst 和 Tungsten 优化);
- 示例:分析电商订单表(有
order_id
/user_id
/amount
等字段),用 DataFrame 的df.groupBy("user_id").agg(sum("amount"))
,代码简洁且性能高。
(5)相互转换
- RDD→DataFrame:需指定 schema,有两种方式:
- 反射推断 schema(适合已知数据结构的场景):scala
import spark.implicits._ // 导入隐式转换 case class User(name: String, age: Int) val rdd = sc.textFile("user.txt").map(line => { val arr = line.split(",") User(arr(0), arr(1).toInt) }) val df = rdd.toDF() // 反射推断schema为User的字段
- 手动定义 schema(适合数据结构复杂或未知的场景):scala
import org.apache.spark.sql.types._ val rdd = sc.textFile("user.txt").map(line => line.split(",")) val schema = StructType(Seq( StructField("name", StringType, nullable = true), StructField("age", IntegerType, nullable = true) )) val df = spark.createDataFrame(rdd.map(row => Row(row(0), row(1).toInt)), schema)
- 反射推断 schema(适合已知数据结构的场景):scala
- DataFrame→RDD:直接调用
rdd
方法,返回RDD[Row]
:scalaval rdd: RDD[Row] = df.rdd // 如需转为具体类型的RDD,需手动提取字段 val userRdd: RDD[User] = rdd.map(row => User(row.getString(0), row.getInt(1)))
补充注意要点
- Spark 版本兼容:DataFrame 在 Spark 1.3 引入,Spark 2.0 后与 Dataset 合并(DataFrame=Dataset [Row]),但 API 仍保持独立,使用时需注意版本差异(如 Spark 1.x 需用
SQLContext
,Spark 2.x + 用SparkSession
); - Python 性能差异:Python 的 RDD 因 JVM-Python 通信开销(序列化 / 反序列化),性能比 Scala RDD 低 3-5 倍;而 Python DataFrame 因 Tungsten 优化(二进制存储,减少通信),性能比 Python RDD 高 2-3 倍,接近 Scala DataFrame;
- 不要过度依赖 RDD:除非处理非结构化数据或自定义逻辑,否则优先用 DataFrame—— 其代码简洁性和性能优势远超过 RDD,尤其是大数据量场景。
5. 排序窗口函数
考察知识点
窗口定义(分区 / 排序)、3 类排序函数差异、实战场景
参考回答
排序窗口函数是 “分区内排序并生成排名” 的函数,核心是 “保留明细数据 + 分组排序”,解决group by
无法保留明细的问题。
(1)基本语法
<函数名> OVER ( PARTITION BY <分区字段> – 按字段分组(可选,默认全表为1个分区) ORDER BY <排序字段> [ASC/DESC] – 分区内排序(必选) )
(2)3 类核心函数差异
函数 | 逻辑 | 示例(成绩排序) |
---|---|---|
row_number() | 唯一排名,无并列 | 95→1,95→2,90→3 |
rank() | 并列排名,跳过空缺 | 95→1,95→1,90→3 |
dense_rank() | 并列排名,不跳过空缺 | 95→1,95→1,90→2 |
(3)实战场景:取每个用户最新 3 笔订单
sql
WITH order_rank AS ( SELECT *, row_number() OVER ( PARTITION BY user_id -- 按用户分区 ORDER BY order_time DESC -- 按时间降序 ) AS rn FROM dwd_trade_order WHERE dt='20240901' ) SELECT order_id, user_id, order_time FROM order_rank WHERE rn <=3; -- 取前3笔
补充注意要点
- 分区键需均匀:避免某分区数据占比超 50%(如按 “地区” 分区,某地区数据过多导致倾斜);
- 过滤前置:先过滤无效数据(如
amount>0
),减少窗口内计算量。
6. Python 的特点
考察知识点
语法特性、生态、执行效率、适用场景
参考回答
Python 是 “简洁、易用、生态丰富” 的解释型语言,核心特点:
(1)语法简洁,开发效率高
- 缩进语法(4 个空格表示代码块),无
{}
/end
; - 动态类型(
a=1
自动为 int,a="hello"
转为 str),无需声明类型; - 语法糖(列表推导式
[x*2 for x in range(5)]
,一行实现复杂逻辑),代码量比 Java 少 30%-50%。
(2)生态丰富,第三方库多
- 数据分析:NumPy(数值计算)、Pandas(DataFrame 操作)、Matplotlib(可视化);
- Web 开发:Django(大型网站)、FastAPI(异步 API);
- AI / 机器学习:TensorFlow/PyTorch(深度学习)、Scikit-learn(传统 ML);
- 大数据:PySpark(Spark Python API)、Dask(并行计算)。
(3)自动内存管理
通过 GC(垃圾回收)自动回收无引用对象,无需手动malloc
/free
,减少内存泄漏风险;但 GC 停顿对实时场景(如高频交易)不友好。
(4)执行效率与优化
- 劣势:解释执行 + GIL(全局解释器锁),CPU 密集型任务比 C++ 慢 50-100 倍,多线程无法真正并行;
- 优化:用 Cython 调用 C 扩展、
multiprocessing
多进程、PyPy
即时编译器(速度比 CPython 快 5-10 倍)。
(5)适用场景
- 优势场景:数据分析、Web API、AI 模型训练、自动化脚本;
- 劣势场景:实时高频系统、千万级并发服务、嵌入式开发。
补充注意要点
- 版本兼容:Python 2 已淘汰,新项目用 Python 3.8+;
- 大数据配合:PySpark 性能比 Scala Spark 低 30%-50%,大数据量优先用 Scala,快速迭代用 Python。
7. is 和 == 的区别
考察知识点
判断逻辑(身份 vs 值)、底层实现、使用场景、误区
参考回答
is
判断 “对象身份”,==
判断 “对象值”,核心差异:
(1)核心定义与底层逻辑
维度 | is(身份判断) | ==(值判断) |
---|---|---|
判断核心 | 变量是否指向同一内存地址(id(a)==id(b) ) |
变量指向的对象内容是否相同 |
底层实现 | 比较id() (内存地址的整数表示) |
调用__eq__() 方法(如列表逐元素比较) |
示例 | a=[1];b=[1]→a is b→False (不同对象) |
a=[1];b=[1]→a==b→True (值相同) |
(2)特殊场景
- 小整数池(-5~256):
a=256;b=256→a is b→True
(缓存复用);a=257;b=257→a is b→False
(不缓存); - 字符串驻留:短字符串(如
"hello"
)驻留复用,a="hello";b="hello→a is b→True
;长字符串不驻留。
(3)使用场景
- 用 is:判断
None
(x is None
,效率高)、单例模式(判断是否同一实例); - 用 ==:比较值(如
user_input == "123456"
)。
(4)常见误区
- 误区 1:用
is
比较值(如a is 10
,大整数不缓存会出错); - 误区 2:认为
==True
则is True
(如[1]==[1]
但[1] is [1]
为 False)。
补充注意要点
is
比==
快(直接比id()
),但仅在判断None
或同一对象时使用;- 不可变对象(int/str)修改会创建新对象,可变对象(list/dict)修改不换地址。
8. SQL 跑得慢,哪里可能有问题
考察知识点
表结构、SQL 逻辑、执行计划、资源配置的常见问题与优化
参考回答
SQL 慢的核心原因分 4 类,排查与优化如下:
(1)表结构设计不合理
- 无索引或索引失效:
- 问题:
WHERE age>30
无age
索引,全表扫描;WHERE SUBSTR(dt,1,8)='20240901'
(函数操作索引字段,索引失效); - 优化:MySQL 建 B + 树索引(
CREATE INDEX idx_age ON user(age)
);避免函数操作索引字段(用dt='20240901'
)。
- 问题:
- 未分区或分区不合理:
- 问题:100GB 订单表未按
dt
分区,查询 “20240901” 数据扫描全表; - 优化:按 “日” 分区(Hive
PARTITIONED BY (dt STRING)
),查询必带dt
过滤。
- 问题:100GB 订单表未按
(2)SQL 语句逻辑差
- 全表扫描 / 无效过滤:
- 问题:
SELECT * FROM order
(无WHERE
)、WHERE 1=1
(无效过滤); - 优化:加
WHERE dt='20240901'
,仅查所需列(SELECT order_id, user_id
)。
- 问题:
- Join 优化差:
- 问题:大表 Join 大表(10 亿行 ×10 亿行)、小表未广播;
- 优化:小表(<10MB)用
broadcast join
(Spark 自动触发),避免 Shuffle;大表按 Join 键分桶。
(3)执行计划不合理
- 全表扫描替代索引扫描:
- 排查:MySQL 用
EXPLAIN
看type
列(ALL
为全表扫描,range
/ref
为索引扫描); - 优化:建合适索引(如
JOIN
字段、WHERE
字段)。
- 排查:MySQL 用
- Sort 操作开销大:
- 问题:
ORDER BY
无索引,大数据量排序耗时久; - 优化:建排序索引(如 MySQL
CREATE INDEX idx_dt_amount ON order(dt, amount)
),利用索引有序性避免排序。
- 问题:
(4)资源配置不足
- CPU / 内存不够:
- 问题:Hive/Spark 任务 Executor 核数少(如 2 核),Task 排队;内存不足导致磁盘溢出;
- 优化:Hive 调大
mapreduce.map.cpu.vcores=4
;Spark 调大-executor-cores 4 --executor-memory 8g
。
- IO 瓶颈:
- 问题:HDFS 存储在 HDD,顺序读速度慢;
- 优化:改用 SSD,或增大 HDFS 块大小(如 128MB→256MB),减少 IO 次数。
补充注意要点
- 优先查执行计划:MySQL 用
EXPLAIN
,Spark 用explain()
,定位全表扫描、Shuffle 等瓶颈; - 数据倾斜:大表 Join 时某 Key 数据过多,需拆分热点 Key(如加盐)或过滤无效数据。
Comments