1. 简单问问项目 -— 略
  2. Spark的执行流程
  3. 宽窄依赖
  4. RDD和Dateframe
  5. 排序窗口函数
  6. python的特点
  7. ·is 和 ==的区别
  8. 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 职责
    1. 初始化 SparkContext(Spark 核心入口,负责与资源管理器通信、创建 RDD、调度任务);
    2. 解析用户代码,生成逻辑执行计划(如 WordCount 的 “读取文件→切分单词→计数→汇总” 步骤);
    3. 向资源管理器申请 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 操作,边为依赖关系:plaintextHadoopRDD(读取文件)→ 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。
  • WordCount 的 Stage 划分
    • Stage 1(Shuffle 前):包含 “读取文件→切分单词→生成 < word,1>”(均为窄依赖),输出中间结果到本地磁盘;
    • Stage 2(Shuffle 后):包含 “reduceByKey 汇总计数→输出结果”(reduceByKey为宽依赖,触发 Shuffle 后执行汇总)。

(4)生成 Task 并调度(Executor 执行准备)

  • 核心动作
    1. Driver 将每个 Stage 拆分为多个Task(任务),Task 数量 = RDD 分区数(如 Stage 1 的 RDD 分区数为 1,生成 1 个 Task;Stage 2 的 ShuffledRDD 分区数默认 200,生成 200 个 Task);
    2. Driver 通过 “任务调度器(TaskScheduler)” 将 Task 分配给 Executor(遵循 “数据本地化” 原则,优先将 Task 分配到数据所在节点,减少网络 IO);
    3. 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 次)。
  • 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)结果汇总与任务结束

  • 核心动作
    1. Stage 2 的 Task 执行完成后,将最终结果(如 <hello,100>、<world,80>)写入目标存储(HDFS/MySQL),或返回给 Driver(如collect操作);
    2. Driver 等待所有 Stage 完成,关闭 SparkContext,释放 Executor 资源,任务结束;
    3. 若任务失败(如多次重试仍失败),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)执行效率差异

  • 窄依赖优势
    1. 无 Shuffle 开销:数据无需跨节点传输,仅在本地执行,IO 和网络成本低;
    2. 流水线执行:多个窄依赖操作可连续执行(如map→filter→flatMap),中间结果无需落地磁盘,直接在内存传递,速度快;
    3. 容错成本低:若某 Task 失败,仅需重新计算其依赖的父 RDD 分区(窄依赖仅一个父分区),无需重新计算整个父 RDD。
  • 宽依赖劣势
    1. Shuffle 开销大:数据需跨节点传输(网络 IO)、写入 / 读取磁盘(磁盘 IO),占任务总耗时的 60%-80%;
    2. 执行顺序受限:宽依赖对应的 Stage 需等待前一个 Stage 完全完成才能启动,无法并行;
    3. 容错成本高:若某 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类型

(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 代码进行 “逻辑计划→优化逻辑计划→物理计划” 的三层优化,自动选择最优执行路径:
    1. 逻辑计划:解析用户代码生成未优化的逻辑计划(如df.filter(col("age")>20).select("name")的逻辑计划是 “读取数据→过滤 age>20→选择 name”);
    2. 优化逻辑计划:Catalyst 自动优化,如 “谓词下推”(将过滤条件age>20下推到数据读取阶段,提前过滤数据,减少后续处理的数据量)、“列裁剪”(仅读取nameage字段,而非全表字段);
    3. 物理计划:将优化后的逻辑计划转为多个物理计划(如 “全表扫描” 或 “索引扫描”),通过成本模型(如数据量、IO 成本)选择最优计划(如若 age 有索引,选择索引扫描;否则全表扫描)。
  • 示例:若用户写df.select("name").filter(col("age")>20),Catalyst 会自动调整为 “先过滤 age>20,再选择 name”,并仅读取nameage字段,比 RDD 的手动操作减少 50%+ 数据量。

② Tungsten 序列化(内存与 CPU 优化)

  • RDD 的序列化问题:RDD 存储的是 Java/Scala 对象,序列化时需保存对象的完整信息(如类名、字段类型、继承关系),序列化后数据量大,且反序列化时需创建大量对象,CPU 开销高;
    • 示例:RDD[(String, Int)]存储 “张三,25”,序列化后需保存 String 和 Int 的对象头、字段值,约 50 字节 / 条;反序列化时需创建 Tuple2 对象,CPU 耗时。
  • DataFrame 的 Tungsten 优化
    1. 二进制存储:DataFrame 的 Row 对象采用 Tungsten 二进制格式存储,直接按字段类型(如 String 用 UTF-8 编码、Int 用 4 字节整数)存储原始数据,无对象头和额外信息,序列化后数据量比 RDD 减少 30%-70%(如 “张三,25” 仅需 10 字节 / 条);
    2. 堆外内存:支持将数据存储在 JVM 堆外内存(避免 JVM GC 对内存的回收开销),适合大数据量场景(如 100GB 数据存储在堆外,GC 时间从分钟级降至秒级);
    3. 向量化执行:按 “批次” 处理数据(如一次处理 1000 条 Row),而非单条处理,减少 CPU 指令跳转和函数调用开销,CPU 利用率提升 2-3 倍。

(4)适用场景差异

  • RDD 适用场景
    1. 非结构化 / 半结构化数据处理(如日志文件、JSON 字符串,需手动解析字段);
    2. 复杂数据类型操作(如嵌套对象、集合,需自定义map/flatMap逻辑);
    3. 底层 API 开发(如自定义 RDD、实现特殊的并行计算逻辑);
    • 示例:处理 APP 埋点日志(每行是 JSON 字符串,需解析 “user_id”“action_type” 等字段),用 RDD 的map(line => JSON.parse(line))手动解析更灵活。
  • DataFrame 适用场景
    1. 结构化数据处理(如 CSV/Parquet 文件、数据库表,有明确字段结构);
    2. SQL 风格操作(如groupBy/join/window函数,适合数据分析师使用);
    3. 高性能要求场景(如大数据量聚合、关联,需 Catalyst 和 Tungsten 优化);
    • 示例:分析电商订单表(有order_id/user_id/amount等字段),用 DataFrame 的df.groupBy("user_id").agg(sum("amount")),代码简洁且性能高。

(5)相互转换

  • RDD→DataFrame:需指定 schema,有两种方式:
    1. 反射推断 schema(适合已知数据结构的场景):scalaimport 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的字段
    2. 手动定义 schema(适合数据结构复杂或未知的场景):scalaimport 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)
  • 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:判断Nonex is None,效率高)、单例模式(判断是否同一实例);
  • 用 ==:比较值(如user_input == "123456")。

(4)常见误区

  • 误区 1:用is比较值(如a is 10,大整数不缓存会出错);
  • 误区 2:认为==Trueis True(如[1]==[1][1] is [1]为 False)。

补充注意要点

  • is==快(直接比id()),但仅在判断None或同一对象时使用;
  • 不可变对象(int/str)修改会创建新对象,可变对象(list/dict)修改不换地址。

8. SQL 跑得慢,哪里可能有问题

考察知识点

表结构、SQL 逻辑、执行计划、资源配置的常见问题与优化

参考回答

SQL 慢的核心原因分 4 类,排查与优化如下:

(1)表结构设计不合理

  • 无索引或索引失效
    • 问题:WHERE age>30age索引,全表扫描;WHERE SUBSTR(dt,1,8)='20240901'(函数操作索引字段,索引失效);
    • 优化:MySQL 建 B + 树索引(CREATE INDEX idx_age ON user(age));避免函数操作索引字段(用dt='20240901')。
  • 未分区或分区不合理
    • 问题:100GB 订单表未按dt分区,查询 “20240901” 数据扫描全表;
    • 优化:按 “日” 分区(HivePARTITIONED BY (dt STRING)),查询必带dt过滤。

(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 用EXPLAINtype列(ALL为全表扫描,range/ref为索引扫描);
    • 优化:建合适索引(如JOIN字段、WHERE字段)。
  • Sort 操作开销大
    • 问题:ORDER BY无索引,大数据量排序耗时久;
    • 优化:建排序索引(如 MySQLCREATE 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(如加盐)或过滤无效数据。