Presto Release 0.257 深度解读:OFFSET 子句、array_position 增强与溢出内存修复实战指南
2026/9/23 10:27:08 网站建设 项目流程

Presto Release 0.257 深度解读:OFFSET 子句、array_position 增强与溢出内存修复实战指南

【免费下载链接】prestoThe official home of the Presto distributed SQL query engine for big data项目地址: https://gitcode.com/gh_mirrors/pre/presto

本指南基于 Presto 官方发布说明 release-0.257.rst 编写,并结合当前仓库源码对每个变更点进行底层原理与实战配置层面的展开。读完本文,你将掌握:如何启用并正确使用新增的OFFSET子句、如何利用array_position的“第 n 次出现”定位能力、以及 0.257 中围绕 Spill 内存、JOIN 死锁、Avro/Kerberos 与动态裁剪等关键修复的来龙去脉,并了解如何安全地规划升级。

一、Release 0.257 概览

Presto Release 0.257 是一次以“稳定性修复 + 查询能力增强”为主基调的版本。整体变更分为两大块:

  • General Changes(通用引擎变更):涵盖内存溢出修复、Spill 场景下的死锁修复、array_position函数增强、相关子查询(correlated subquery)支持复杂表达式、以及全新的OFFSET子句支持等。
  • Hive Changes(Hive 连接器变更):修复 Avro 格式表在 Kerberos 启用的 HDFS 上读取 schema 的缺陷,以及 Hive 分区动态裁剪对 null 键的处理。

下文将按“新增能力 → 稳定性修复 → Hive 修复”的顺序逐项展开,并在每个主题下给出仓库中的源码与测试依据。

二、重点新能力:OFFSET 子句

这是 0.257 中最具实用价值的功能点。发布说明原文为:

Add support for theOFFSETclause in SQL query expressions. This feature can be enabled by setting the session propertyoffset_clause_enabledor configuration propertyoffset-clause-enabledtotrue.

也就是说,OFFSET 子句从 0.257 开始正式纳入 SQL 语法支持,但默认处于关闭状态,需要显式开启后才能使用。

2.1 如何启用 OFFSET

启用方式有两种,任选其一:

方式一:Session 级启用(单查询生效)

SET SESSION offset_clause_enabled = true; SELECT * FROM orders ORDER BY orderkey OFFSET 10 ROWS;

方式二:配置级启用(全局生效)

在协调节点(Coordinator)与工作节点(Worker)的config.properties中加入:

offset-clause-enabled=true

从源码看,该配置项定义于 FeaturesConfig.java:

@Config("offset-clause-enabled") @ConfigDescription("Enable support for OFFSET clause") public FeaturesConfig setOffsetClauseEnabled(boolean offsetClauseEnabled) { this.offsetClauseEnabled = offsetClauseEnabled; return this; }

而对应的 Session 属性在 SystemSessionProperties.java 中声明为OFFSET_CLAUSE_ENABLED = "offset_clause_enabled",并在注册处(同文件 L1584-L1587)与FeaturesConfig.isOffsetClauseEnabled()绑定,默认值取配置项的值。

值得注意的是:在较新的代码中该 Session 属性注册时默认值直接取featuresConfig.isOffsetClauseEnabled(),意味着只要配置文件开了开关,所有会话默认即启用。

2.2 SQL 语法层面:OFFSET 长什么样

在 0.257 中,OFFSET 的语法形态由 ANTLR 文法 SqlBase.g4 定义:

(ORDER BY sortItems)? (OFFSET offset=INTEGER_VALUE (ROW | ROWS)?)? (LIMIT limit=INTEGER_VALUE | FETCH FIRST limit=INTEGER_VALUE (ROW | ROWS) ONLY)?

也就是说,OFFSET出现在ORDER BY之后、LIMIT之前,行数必须是整数字面量,且可以带可选的ROWROWS单位(如OFFSET 10 ROWSOFFSET 10 ROWOFFSET 10均合法)。

解析阶段由 AstBuilder.java 处理:

Optional<Offset> offset = Optional.empty(); if (context.OFFSET() != null) { offset = Optional.of(new Offset( Optional.of(getLocation(context.OFFSET())), getTextIfPresent(context.offset).orElseThrow(() -> new IllegalStateException("Missing OFFSET row count")))); }

生成的 AST 节点Offset作为Query树的一个组成部分(见 Query.java),并在格式化回 SQL 时输出为OFFSET <count> ROWS(见 SqlFormatter.java)。

2.3 语义校验:行数必须非负

在分析阶段,StatementAnalyzer.java 会对 OFFSET 行数做合法性校验,违反时抛出INVALID_OFFSET_ROW_COUNT语义错误:

throw new SemanticException(INVALID_OFFSET_ROW_COUNT, node, "Invalid OFFSET row count: %s", node.getRowCount()); throw new SemanticException(INVALID_OFFSET_ROW_COUNT, node, "OFFSET row count must be greater or equal to 0 (actual value: %s)", rowCount);

即:OFFSET 的行数必须是大于等于 0 的整数。

2.4 执行计划层面:OffsetNode 与优化规则

逻辑计划中使用专门的OffsetNode表示跳过前 N 行,节点内部保存count字段并要求count >= 0(见 OffsetNode.java)。

围绕该节点,0.257 引入了一组迭代优化规则:

  • ImplementOffset.java:把OffsetNode改写为Project → Filter(rowNumber > x) → RowNumber三节点结构。其 Javadoc 明确说明该改写依赖RowNumberNode保持输入顺序的特性,因此当查询含ORDER BY时,排序后的行顺序会被保留,再裁掉前 x 行。
  • PushLimitThroughOffset.java:当LIMITOFFSET共存时,将LIMIT下推经过OFFSETLIMIT n变为LIMIT n + offset),从而让上游尽早裁剪数据。
  • PushOffsetThroughProject.java:将OFFSET下推穿透Project节点,减少中间结果。

同时,SimplifyPlanWithEmptyInput等优化器也会感知OffsetNode(见 SimplifyPlanWithEmptyInput.java),在输入为空时提前化简。

重要ImplementOffset在应用时会检查 Session 属性,若未开启 OFFSET 支持会直接抛出PrestoException(错误码NOT_SUPPORTED,错误信息 "Offset support is not enabled")。这一点在测试 TestImplementOffset.java 中有完整覆盖:

@Test(expectedExceptions = PrestoException.class, expectedExceptionsMessageRegExp = "Offset support is not enabled") public void testOffsetClauseDisabled()

而开启属性后,OFFSET 2会被改写为row_num > BIGINT '2'的 Filter 加 RowNumber 计划(见同文件 testReplaceOffsetOverValues)。

2.5 OFFSET 使用示例

-- 开启会话级开关 SET SESSION offset_clause_enabled = true; -- 跳过前 10 行取后续数据 SELECT orderkey, totalprice FROM orders ORDER BY orderkey OFFSET 10 ROWS; -- OFFSET 与 LIMIT 组合实现分页(第 3 页,每页 20 条) SELECT * FROM orders ORDER BY orderkey OFFSET 40 ROWS LIMIT 20; -- 不带 ROWS/ROW 单位也合法 SELECT name FROM nation OFFSET 2;

在 0.257 中该能力默认关闭,升级后如需使用请务必先设置offset-clause-enabled=true(全局)或在会话中开启offset_clause_enabled,否则会得到 "Offset support is not enabled" 错误。

三、重点新能力:array_position 支持“第 n 次出现”

发布说明原文:

Add support to find the n-th instance in :func:!array_position.

3.1 功能语义

array_position(array(T), element)原本只能返回元素第一次出现的位置(1-based),未找到返回 0。0.257 为其增加了第三个可选参数instance,用于定位第 n 次出现

  • instance > 0:从数组头部向尾部扫描,返回第 n 次出现的位置;
  • instance < 0:从数组尾部向头部扫描,返回倒数第 n 次出现的位置;
  • instance = 0:非法,抛出INVALID_FUNCTION_ARGUMENT异常;
  • 若不足 n 次出现,返回 0。

3.2 源码实现

新增实现位于 ArrayPositionWithIndexFunction.java,与原有 ArrayPositionFunction.java 共用array_position函数名(重载),并在 BuiltInTypeAndFunctionNamespaceManager.java 中注册。核心扫描逻辑(以 LONG 元素重载为例,L91-L120):

int size = array.getPositionCount(); int instancesFound = 0; if (instance == 0) { throw new PrestoException(INVALID_FUNCTION_ARGUMENT, "array_position cannot take a 0-valued instance argument."); } int startIndex = instance > 0 ? 0 : size - 1; int stopIndex = instance > 0 ? size : -1; int stepSize = instance > 0 ? 1 : -1; instance = Math.abs(instance); for (int i = startIndex; i != stopIndex; i += stepSize) { if (!array.isNull(i)) { long arrayValue = type.getLong(array, i); Boolean result = (Boolean) equalMethodHandle.invoke(arrayValue, element); checkNotIndeterminate(result); if (result) { instancesFound++; if (instancesFound == instance) { return i + 1; // result is 1-based (instead of 0) } } } } return 0;

实现细节值得注意:

  1. 方向控制:通过instance的正负决定起点、终点和步长,负数场景从size - 1反向扫描,实现“倒数第 n 次”。
  2. 类型泛化:同一函数针对 boolean、long、double、Slice 等类型提供多个@SqlType重载(T泛型 +@TypeParameter),元素相等性通过@OperatorDependency(operator = EQUAL)注入的equalMethodHandle比较,支持任意可比较的数组元素类型。
  3. NULL 跳过:数组中的 NULL 元素被跳过,不参与计数。
  4. 1-based 返回:命中时返回i + 1,与 SQL 数组下标习惯一致;未命中返回 0。

3.3 使用示例

SELECT array_position(ARRAY['a', 'b', 'a', 'c', 'a'], 'a'); -- 返回 1(第一次出现) SELECT array_position(ARRAY['a', 'b', 'a', 'c', 'a'], 'a', 2); -- 返回 3(第二次出现) SELECT array_position(ARRAY['a', 'b', 'a', 'c', 'a'], 'a', 3); -- 返回 5(第三次出现) SELECT array_position(ARRAY['a', 'b', 'a', 'c', 'a'], 'a', -1); -- 返回 5(倒数第一次出现) SELECT array_position(ARRAY['a', 'b', 'a', 'c', 'a'], 'a', -2); -- 返回 3(倒数第二次出现) SELECT array_position(ARRAY['a', 'b'], 'z', 1); -- 返回 0(未找到) SELECT array_position(ARRAY['a', 'b'], 'a', 0); -- 报错:0 is an invalid instance position for array_position

该能力对“重复事件序列中定位特定次数事件”的场景(如日志分析、时序数据去重取第 N 条)非常实用,且对 BOOLEAN、BIGINT、DOUBLE、VARCHAR 等主流类型均有重载支持。

四、General Changes 稳定性与正确性修复

0.257 同时包含一批针对 Spill、内存跟踪与表达式正确性的修复,建议升级用户在压测时重点回归以下场景。

4.1 Spill 相关的三个修复

  • Fix queries failing withEXCEEDED_LOCAL_MEMORY_LIMITerror due to incorrect memory tracking while reading spilled data.
  • Fix deadlock for queries withJOINandLIMITwith spilling enabled.
  • Improve memory usage of queries spilling in the join operator.

这三条均围绕Spill(溢写)机制:

  • 读取溢写数据时内存跟踪错误导致EXCEEDED_LOCAL_MEMORY_LIMIT:0.256 及更早版本中,算子从磁盘回读溢写数据(如排序、聚合、JOIN 的 spill file)时,其内存占用未被正确计入本地内存池,导致查询被误判为超限而失败。0.257 修正了该路径的内存记账,消除了这类“假阳性”超限。
  • JOIN + LIMIT + Spill 组合下的死锁:当查询同时包含 JOIN 与 LIMIT 且开启了 Spill 时,可能因算子间相互等待(如 JoinOperator 等待 spill 完成、LimitOperator 等待输入)而进入死锁。0.257 修复了该调度时序问题。
  • JOIN 算子溢写内存优化:改进了 Hash Join 在溢写场景下的内存分配策略,减少不必要的缓冲占用。

建议升级后在开启spill-enabled=true的环境中对 JOIN、GROUP BY、ORDER BY 混合 LIMIT 的查询做回归验证。

4.2 表达式正确性:同一变量多个 IS NULL 判断

Fix query failures due to expressions with multipleis nullchecks on the same variable.

修复了同一表达式(如 CASE、复杂 WHERE 条件)中对同一个变量多次做IS NULL判断时可能触发的查询失败。该问题与常量折叠、IS NULL谓词下推及去重优化相关,0.257 修正了这类表达式的求值逻辑,确保如WHERE (a IS NULL) OR (a IS NOT NULL AND b > 1)这类写法稳定执行。

4.3 查询计划可读性:表达式打印更接近合法 SQL

Improve expressions printed in query plans to be closer to valid SQL.

0.257 改进了EXPLAIN输出中表达式(尤其是一元/二元运算、类型转换与函数调用)的格式化逻辑,使计划中的表达式更接近可直接执行的合法 SQL 形态。这对使用EXPLAIN (FORMAT JSON)/EXPLAIN ANALYZE做性能分析与问题排查的团队是直接收益——计划更易读、更易复制回放。

4.4 相关子查询:支持相关条件中的复杂表达式

Add support for correlated subqueries with complex expressions in the correlation.

相关子查询(correlated subquery)此前要求相关列(correlation)为简单列引用;0.257 扩展为允许在相关条件中使用复杂表达式。例如:

SELECT o.custkey, (SELECT max(l.extendedprice) FROM lineitem l WHERE l.orderkey = o.orderkey + 100) AS adjusted_max FROM orders o;

这里子查询的相关条件l.orderkey = o.orderkey + 100中出现了算术表达式o.orderkey + 100(而不仅是o.orderkey),0.257 起的分析器与去关联(decorrelation)流程可以正确处理此类形态。这条能力的价值在于:许多“基于派生键关联”的业务 SQL(如 join key 需要变换)从此可以直接写成相关子查询,而不必改写成 JOIN 或 CTE。

五、Hive 连接器修复

  • Fix a bug in reading Avro format table with schema located in a Kerberos enabled HDFS compliant filesystem.
  • Fix dynamic pruning for null keys in hive partition.

5.1 Avro 表 + Kerberos HDFS 读取修复

在 Kerberos 启用的 HDFS 兼容文件系统上,若 Avro 表的 schema 存放在 HDFS 中(avro.schema.url指向 HDFS 路径),0.257 之前存在读取缺陷(涉及 delegate 文件系统的认证上下文传递),导致 schema 拉取失败或表读取异常。0.257 修复了该场景下通过 HiveMetastore/文件系统访问 Avro schema 的路径,使得“Kerberos 安全集群 + Avro schema 外置”的组合可以正常工作。

5.2 分区动态裁剪:null 键修复

修复了 Hive 分区动态裁剪(dynamic partition pruning)在分区键为 NULL时的缺陷。此前当查询针对分区列使用可推导为 NULL 的过滤条件(或裁剪值包含 NULL)时,动态裁剪可能产生错误的裁剪集合或遗漏分区。0.257 修正了对 null 分区键的裁剪处理,避免结果缺失或多余扫描。

六、升级与回归建议

结合上述变更,升级到 0.257 前后建议按如下清单操作:

  1. 功能开关:若业务需要使用OFFSET,在config.properties中配置offset-clause-enabled=true;若只想局部试用,使用SET SESSION offset_clause_enabled = true;。注意两处配置名的连字符/下划线区别。
  2. Spill 场景回归:对开启spill-enabled的集群,重点回归JOIN/GROUP BY/ORDER BYLIMIT组合的查询,确认不再出现死锁与EXCEEDED_LOCAL_MEMORY_LIMIT
  3. Kerberos 集群回归:Hive 使用 Avro 表且 schema 存放于 HDFS 的表,在安全集群上验证可正常读取。
  4. 分区裁剪回归:对分区列为 NULL 的场景(如WHERE partition_col IS NULL或裁剪结果含 NULL)做正确性验证。
  5. 函数行为确认array_position新增了第三参数重载,原有两参数调用行为不变,可放心兼容;新增的“第 n 次 / 倒数第 n 次”语义可直接用于业务 SQL。

七、总结

Presto Release 0.257 在查询表达能力稳定性两个维度都有实质提升:

  • OFFSET 子句补齐了标准 SQL 分页语法(配合LIMIT使用),其实现(OffsetNode+ImplementOffset/PushLimitThroughOffset/PushOffsetThroughProject规则 +RowNumber改写)在源码与单测中均有完整闭环,可放心启用;
  • array_position三参数重载提供了正/反向“第 n 次出现”定位能力,实现上对多种元素类型泛化支持,NULL 语义清晰;
  • 多个 Spill 内存与死锁修复显著改善了开启溢写场景下的查询稳定性;
  • Hive 侧的 Avro/Kerberos 与动态裁剪 null 键修复,则让安全集群与边界数据场景更加可靠。

如需深入实现细节,可继续阅读:FeaturesConfig.java、ImplementOffset.java、ArrayPositionWithIndexFunction.java,以及对应测试 TestImplementOffset.java 与 TestPushLimitThroughOffset.java。

【免费下载链接】prestoThe official home of the Presto distributed SQL query engine for big data项目地址: https://gitcode.com/gh_mirrors/pre/presto

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询