一张几十行的表也要等很久,多半不是表达式慢,而是计划里仍有一次洗牌。Spark 4.3 给这种小片段加了一条可选路径:符合条件时用单个任务做完,不再洗牌。限度写得很死。另外几项 SQL 能力标着预览,行为以后还可能改。升级到这一版,不等于小查询和半结构化类型都已经换了默认。
免洗牌覆盖的范围
| 条件 | 可以不洗牌 | 仍要洗牌 |
|---|---|---|
| 分区 | 只有 1 个 | 多于 1 个 |
| 体积 | 一个非空小文件,或内存不超过 1000 行 | 超出该限度 |
| 开关 | 显式打开 | 保持默认 |
| 多个分区的小表 | 不符合 | 仍走普通洗牌 |
这条路径对应 SPARK-56978,只处理有资格的、单一分区的小片段,执行落在一个任务上。默认限度来自 SPARK-57851:一个分区里只有一个非空的小文件,或者本地内存中的行数不超过 1000。分区多于一个,或者行数超过限度,仍然洗牌。这是显式选择的执行策略,不是版本升级后的新默认。
判断时看计划,不看表名里的“小”。小文件若被拆成多个分区,行数即使很少也不符合。内存里的临时结果超过 1000 行,同样退出这条路径。打开之后只对比这一条查询的耗时,不要用它解释整库的平均时间。限度是默认值,调大之前先确认这一条查询的正确性没有依赖洗牌带来的分区边界。
缓存序列化是另一个开关
列式缓存可以改用 Arrow 批次序列化。配置项是 spark.sql.cache.serializer,类名是 org.apache.spark.sql.execution.columnar.ArrowCachedBatchSerializer,对应 SPARK-57268。它同样要显式设置。没有设置时,缓存仍是原来的格式。查询变慢,先打印实际生效的类名,再比较打开前后的时间。类名没变,就不是这条开关的问题。
预览不要进契约
ASOF JOIN 用来做时点匹配:给定一个时刻,找另一张表里不晚于该时刻的最近一行。它在这一版是预览。时间类型方面,TIMESTAMP_NTZ 和 TIMESTAMP_LTZ 可以写到小数秒精度 7 至 9,也是预览,后续版本可能调整。对外交换的字段仍用已经稳定的时间类型,预览类型只留在试验作业里。
VARIANT 上的变换,以及 JSON_TABLE,解决的是把半结构化内容展开成行。展开是否正确,要拿固定的样例文档对照列,而不是看连接有没有被消除。连接消除属于约束和优化器,和这里的解析不是同一张验收单。样例文档里的空字段、重复键和嵌套数组都要有一条,避免只验证了扁平的对象。
Spark Connect 可以用 REST 列出会话和操作,并拿到操作编号,方便对上一次慢查询是哪一次提交。实验性的会话复用只在 POSIX 上通过环境变量 SPARK_LOCAL_CONNECT_REUSE 设为 1 来打开。跨平台的启动脚本不要依赖它。发布节奏从大约半年一次改为按季度,相邻版本里这类开关会更常出现。升级记录里把默认关闭和仍是预览分成两列。
一条慢查询的顺序
先看分区数和行数是否同时落在限度内。落不进,就去减分区或接受洗牌,不要先改 SQL 文本。落得进,再打开免洗牌,只重跑这一条,记下计划里洗牌是否消失。若时间仍长,再查缓存序列化的类名。最后看语句是否用了 ASOF 或高精度时间戳。用了,就把它移出契约作业,单独标预览。
常见问题 FAQ
升级到 Spark 4.3 后,小查询会自动免洗牌吗?
不会。这条路径要显式打开,并且只覆盖单分区、一个非空小文件或内存不超过 1000 行的片段。
行数很少但分成了四个分区,能走吗?
不能。分区数大于一就不在默认限度里。先看计划里的分区数,而不是只看行数。
Arrow 缓存和免洗牌是同一个开关吗?
不是。缓存序列化看 spark.sql.cache.serializer 是否指向 Arrow 的批次类。免洗牌看的是小片段执行策略。两个都要分别打开、分别计时。
ASOF JOIN 可以写进对外字段说明吗?
这一版它是预览,以后可能改行为。对外契约用已经稳定的时间类型和普通连接,时点匹配留在试验作业。
会话复用的环境变量能写进所有机器的启动脚本吗?
不能。SPARK_LOCAL_CONNECT_REUSE 标的是实验能力,而且只在 POSIX 上。其他系统不要靠它复用连接。