增强版嵌套列裁剪功能

更新时间:
复制 MD 格式

功能概述

增强版嵌套列裁剪(Enhanced Nested Column Pruning)是 MaxCompute 面向复杂类型数据查询提供的一项性能优化能力。

当表中包含 STRUCTARRAY<STRUCT>MAP<STRING, STRUCT> 或多层组合嵌套类型时,查询通常只会访问其中少量字段。开启增强版嵌套列裁剪后,MaxCompute 可以在读取数据时只扫描实际需要的子字段,避免读取未使用的嵌套字段,从而减少存储 I/O、降低内存开销并提升查询性能。

例如,表中有一个复杂类型列 info STRUCT<a:STRING, b:BIGINT, c:DOUBLE>,如果查询只使用 info.b,系统将只读取 info.b,不会读取 info.ainfo.c

开启方式

在 SQL 会话中设置以下参数即可开启增强版嵌套列裁剪:

SET odps.sql.nested.type.enable.enhanced.cp=true;

适用场景

该功能适合以下类型的业务场景:

  • 表中存在较宽的复杂类型列,例如一个 STRUCT 中包含几十个字段。

  • 查询只访问复杂类型中的部分子字段。

  • 数据结构存在多层嵌套,例如 STRUCT 中包含 ARRAY<STRUCT>MAP<STRING, STRUCT>

  • 查询中包含 Join、聚合、窗口函数、CTE、UNNEST、LATERAL VIEW EXPLODE 等复杂 SQL 逻辑。

  • 希望在不修改表结构、不迁移数据的情况下提升复杂类型查询性能。

支持数据类型

增强版嵌套列裁剪支持以下复杂类型及其组合:

类型

示例

STRUCT

STRUCT<a:STRING, b:BIGINT>

ARRAY<STRUCT>

ARRAY<STRUCT<name:STRING, price:BIGINT>>

MAP<STRING, STRUCT>

MAP<STRING, STRUCT<a:STRING, b:BIGINT>>

多层嵌套组合

ARRAY<MAP<STRING, STRUCT<a:STRING, b:BIGINT>>>

功能效果

开启增强版嵌套列裁剪后,可以获得以下收益:

  • 减少数据读取量:只读取查询实际需要的嵌套子字段。

  • 降低内存占用:未使用字段不会进入后续执行计划。

  • 缩短查询耗时:宽复杂类型列、深层嵌套列场景收益更明显。

  • 扩大优化覆盖范围:相比旧版裁剪能力,增强版可以覆盖更多复杂 SQL 场景。

如果一张表的复杂类型列中包含 50 个字段,而查询只使用其中 2 到 3 个字段,增强版嵌套列裁剪可以显著减少扫描和反序列化开销。

与旧版裁剪能力的区别

旧版嵌套列裁剪主要适用于简单的 Project -> TableScan 查询模式,例如直接查询 s.a。增强版嵌套列裁剪可以分析完整执行计划,因此覆盖范围更广。

能力

旧版

增强版

简单 STRUCT 字段裁剪

支持

支持

深层嵌套 STRUCT

不支持

支持

ARRAY<STRUCT>MAP<STRUCT> 等复杂组合

不支持

支持

Join 中使用嵌套字段

不支持

支持

窗口函数中使用嵌套字段

不支持

支持

CTE 或公共子表达式复用

不支持

支持

聚合、ROLLUP、GROUPING SETS

不支持

支持

UNNEST 子查询

不支持

支持

LATERAL VIEW EXPLODE

不支持

支持

INSERT INTO / CTAS 源表裁剪

不支持

支持

数据脱敏场景

不支持

支持

使用示例

STRUCT 字段裁剪

最基础的场景:只查询 STRUCT 中的部分字段时,裁剪掉未使用的字段。

-- 表定义:c3 struct<a:string, b:bigint, c:double>
-- 实际只读取 c3.a,裁剪掉 b 和 c
SELECT c3.a FROM test;
-- TableScan schema: c3:struct<a:STRING>

多字段合并裁剪

同时访问同一 STRUCT 的多个字段时,只保留用到的字段。

-- 表定义:c3 struct<a:string, b:bigint, c:double>
SELECT c3.a, c3.b FROM test;
-- TableScan schema: c3:struct<a:STRING, b:BIGINT>
-- c 字段被裁剪

深层嵌套 STRUCT 裁剪

支持任意深度的嵌套 STRUCT 字段裁剪。

-- 表定义:c3 struct<a:string, b:bigint, c:struct<c_a:string, c_b:bigint>>

-- 只取深层字段
SELECT c3.c.c_a FROM test;
-- TableScan schema: c3:struct<c:struct<c_a:STRING>>

-- 同时取不同深度的字段,自动合并
SELECT c3.c.c_a, c3.c FROM test;
-- TableScan schema: c3:struct<c:struct<c_a:STRING, c_b:BIGINT>>

ARRAY 下标访问裁剪

通过下标访问 ARRAY 中的特定字段时,可裁剪未使用的 STRUCT 字段。

-- 表定义:c2 array<struct<a:string, b:bigint>>,
--          c3 array<struct<a:string, b:struct<a:string, b:bigint>>>
SELECT c2[0].b, c3[0].b.b FROM test;
-- TableScan schema:
--   c2:array<struct<b:BIGINT>>
--   c3:array<struct<b:struct<b:BIGINT>>>
-- a 字段被裁剪

支持多层嵌套数组:

-- 表定义:c2 array<array<struct<a:string, b:struct<a:string, b:bigint>>>>
SELECT (c2[0])[0].b.b FROM test;
-- TableScan schema: c2:array<array<struct<b:struct<b:BIGINT>>>>

MAP<STRING, STRUCT> 裁剪

通过 map_values() 访问 MAP 值中的 STRUCT 字段时,可裁剪未使用字段。

-- 表定义:c2 map<string, struct<a:string, b:bigint>>,
--          c3 map<string, struct<a:string, b:struct<a:string, b:bigint>>>
SELECT MAP_VALUES(c2)[0].b, MAP_VALUES(c3)[0].b.b FROM test;
-- TableScan schema:
--   c2:map<STRING, struct<b:BIGINT>>
--   c3:map<STRING, struct<b:struct<b:BIGINT>>>

复杂嵌套组合裁剪

支持 ARRAY、MAP、STRUCT 任意嵌套组合的裁剪。

-- array<map<string, struct<...>>>
SELECT MAP_VALUES(c2[0])[0].b.b FROM test;
-- TableScan schema: c2:array<map<STRING, struct<b:struct<b:BIGINT>>>>

-- array<struct<..., b:map<string, struct<...>>>>
SELECT MAP_VALUES(c2[0].b.b)[0].b FROM test;
-- TableScan schema: c2:array<struct<b:struct<b:map<STRING, struct<b:STRING>>>>>

-- struct<a:string, b:array<struct<...>>>
SELECT (c2.b)[0].b FROM test;
-- TableScan schema: c2:struct<b:array<struct<b:BIGINT>>>

带 WHERE 过滤条件的裁剪

SELECT 和 WHERE 使用不同嵌套字段时,两者取并集。

-- 表定义:c3 struct<a:string, b:bigint, c:double>
SELECT c3.a FROM test WHERE c1 > 'xx';
-- TableScan schema: c1:STRING, c3:struct<a:STRING>

-- 嵌套字段在 WHERE 中
SELECT c3.a FROM test WHERE c3.b > 100;
-- TableScan schema: c3:struct<a:STRING, b:BIGINT>

带 FILTER 下推的裁剪

嵌套字段过滤条件可下推到存储层,同时保持列裁剪效果。

-- 表定义:extension struct<col1:string, col2:struct<c21:string, c22:string, c23:string>, col3:bigint>
SELECT id, extension.col1 FROM test WHERE extension.col2.c22 = '1';
-- TableScan schema: id:BIGINT, extension:struct<col1:STRING, col2:struct<c22:STRING>>
-- 过滤条件下推到存储层,同时只读取需要的嵌套字段

JOIN 场景裁剪

JOIN 查询中,对参与 JOIN 的每张表分别进行嵌套字段裁剪。

SELECT t2.id, meta.m2, extension.e2
FROM t_row_policy t1 JOIN nested_src t2 ON t1.id = t2.id AND t1.extension.e3 = t2.key;
-- t_row_policy TableScan: id:BIGINT, extension:struct<e2:BIGINT, e3:STRING>, meta:struct<m2:BIGINT>

-- LEFT SEMI JOIN
SELECT meta.m2, extension.e2
FROM t_row_policy t1 LEFT SEMI JOIN nested_src t2 ON t1.id = t2.id AND t1.extension.e3 = t2.key;
-- TableScan: id:BIGINT, extension:struct<e2:BIGINT, e3:STRING>, meta:struct<m2:BIGINT>

-- MAP JOIN
SELECT /*+mapjoin(t2) */ t2.id, meta.m2, extension.e2
FROM t_row_policy t1 JOIN nested_src t2 ON t1.id = t2.id AND t1.extension.e3 = t2.key;
-- TableScan: id:BIGINT, extension:struct<e2:BIGINT, e3:STRING>, meta:struct<m2:BIGINT>

聚合(GROUP BY / ROLLUP / GROUPING SETS)裁剪

-- GROUP BY
SELECT extension.e2, MAX(meta.m2) AS meta FROM t_row_policy GROUP BY extension.e2;
-- TableScan: extension:struct<e2:BIGINT>, meta:struct<m2:BIGINT>

-- ROLLUP
SELECT extension.e1, COUNT(1) FROM t_row_policy GROUP BY ROLLUP(extension.e1, extension.e3);
-- TableScan: extension:struct<e1:STRING, e3:STRING>

-- GROUPING SETS
SELECT extension.e1, COUNT(1) FROM t_row_policy GROUP BY GROUPING SETS (extension.e1, extension.e3);
-- TableScan: extension:struct<e1:STRING, e3:STRING>

窗口函数裁剪

-- 基本窗口函数
SELECT extension.e1,
       ROW_NUMBER() OVER (PARTITION BY id ORDER BY extension.e2) AS rn
FROM t_row_policy WHERE extension.e3 = 'test';
-- TableScan: id:BIGINT, extension:struct<e1:STRING, e2:BIGINT, e3:STRING>

-- 聚合窗口
SELECT extension.e1,
       SUM(extension.e2) OVER (PARTITION BY meta.m1) AS total
FROM t_row_policy;
-- TableScan: extension:struct<e1:STRING, e2:BIGINT>, meta:struct<m1:STRING>

-- 深层嵌套字段
SELECT extension.e1,
       MAX(extension.e2.e22.e221) OVER (PARTITION BY extension.e3) AS max_val
FROM t_row_policy;
-- TableScan: extension:struct<e1:STRING, e2:struct<e22:struct<e221:STRING>>, e3:STRING>

ORDER BY / SORT BY / DISTRIBUTE BY 裁剪

-- ORDER BY
SELECT c1.c2.c3 FROM test ORDER BY c1.c2.c2 LIMIT 10;
-- TableScan: c1:struct<c2:struct<c2:STRING, c3:STRING>>

-- SORT BY
SELECT extension.e3 FROM t_row_policy SORT BY extension.e2;
-- TableScan: extension:struct<e2:BIGINT, e3:STRING>

-- DISTRIBUTE BY
SELECT extension.e3 FROM t_row_policy DISTRIBUTE BY extension.e2;
-- TableScan: extension:struct<e2:BIGINT, e3:STRING>

CTE(WITH 子句)裁剪

WITH t AS (SELECT extension FROM t_row_policy WHERE id = 1)
SELECT extension.col1 FROM t
UNION ALL
SELECT extension.col3 FROM t;
-- TableScan: id:BIGINT, extension:struct<col1:STRING, col3:STRING>

UNNEST / LATERAL VIEW 裁剪

-- LATERAL VIEW EXPLODE
SELECT t.extension.e1, lv.a1
FROM t_row_policy t
LATERAL VIEW EXPLODE(t.name) lv AS a1
WHERE t.extension.e2 > 10;
-- TableScan: name:array<struct<a1:STRING, a2:STRING>>, extension:struct<e1:STRING, e2:BIGINT>

-- LEFT JOIN UNNEST
SELECT extension.e2 FROM t_row_policy LEFT JOIN UNNEST(name) WHERE a1 = '12';
-- TableScan: name:array<struct<a1:STRING>>, extension:struct<e2:BIGINT>

-- UNNEST + 子查询
SELECT extension.e3 AS c1,
       (SELECT MAX(r2) FROM UNNEST(extension.e2)) AS c2,
       (SELECT MAX(r3) FROM UNNEST(extension.e2)) AS c3,
       id
FROM t_row_policy;
-- TableScan: id:BIGINT, extension:struct<e2:array<struct<r2:STRING, r3:STRING>>, e3:STRING>

-- 多层 UNNEST
SELECT extension2.e3, rr2.r22
FROM t_row_policy
JOIN UNNEST(extension2.e2) AS r
JOIN UNNEST(r.r2) AS rr2
WHERE rr2.r21 > '1';
-- TableScan: extension2:struct<e2:array<struct<r2:array<struct<r21:STRING, r22:BIGINT>>>>, e3:STRING>

函数表达式裁剪

嵌套字段作为函数参数时,只读取用到的字段。

SELECT CONCAT(extension.e3, '-'), id FROM t_row_policy;
-- TableScan: id:BIGINT, extension:struct<e3:STRING>

SELECT CONCAT(SUBSTR(extension.col2.c22, 1, 2), '-'), id FROM t_row_policy;
-- TableScan: id:BIGINT, extension:struct<col2:struct<c22:STRING>>

SELECT CONCAT(extension.e1, CAST(extension.e2 AS STRING)) AS combined
FROM t_row_policy WHERE extension.e2 > 10;
-- TableScan: extension:struct<e1:STRING, e2:BIGINT>

数据脱敏(Data Masking)场景裁剪

在列上配置了数据脱敏策略后,增强版裁剪依然生效。

-- extension 列配置了脱敏策略
SELECT COUNT(1) FROM t_row_policy WHERE extension.col2 > 20L;
-- TableScan schema: extension:struct<col2:BIGINT>
-- 脱敏策略正常工作,同时只读取 col2 字段

-- 深层嵌套脱敏
SELECT COUNT(1) FROM t_row_policy WHERE extension.col2.c22 > 20L;
-- TableScan schema: extension:struct<col2:struct<c22:BIGINT>>

不会触发裁剪的场景

在以下场景中,系统需要读取完整复杂类型列,因此不会进行嵌套字段裁剪:

场景

原因

SELECT * FROM t

查询需要所有列和所有字段

SELECT extension FROM t

查询需要完整 STRUCT

SELECT to_json(extension) FROM t

序列化函数需要完整结构

查询逻辑引用了整个复杂类型列

系统无法只保留部分子字段

UNNEST 需要输出完整数组元素

完整数组元素是查询结果的一部分

GROUPING SETS 中使用整个 STRUCT

无法确定可裁剪的字段子集

窗口函数或表达式中使用 SELECT *

所有字段都被视为需要保留

这些限制是预期行为。只有当优化器能够确定某些子字段不会被查询使用时,才会进行裁剪。