在包含宽列的 TopN 等查询中,宽列会随 Scan、Sort、Limit、Shuffle 等算子全程搬运,即使最终只返回少量行,也需要读取、解压和传输大量宽列数据。LazyProjection(延迟物化)让下层算子只携带小列和行定位信息,在得到最终结果行后再回表读取宽列,从而显著减少 I/O 和网络开销。该功能自 Hologres V5.0.0 起支持,默认开启。
背景信息
应用场景
LazyProjection 适用于以下典型场景:
-
宽表分析:表中包含大量列或长文本、JSON 等宽列,查询只需返回排序后的少量明细。
-
日志检索:按时间排序取最近若干条日志,并返回完整的日志内容。
-
TopN 查询:
ORDER BY ... LIMIT N类查询,N 远小于参与排序的行数。 -
过滤后回表:先按小列过滤、排序,再取出少量命中行的宽列。
-
向量检索二阶段取数:先基于向量距离等小列得到 TopN 候选,再取回候选行的原文、属性等宽列。
这些场景的共同特点是:参与计算(过滤、排序、截断)的行数多,最终输出的行数少,且输出结果中包含宽列。
未开启 LazyProjection 时的问题
未开启 LazyProjection 时,执行计划通常为 Scan → Sort → Limit → Shuffle。宽列从 Scan 阶段起就被读取、解压,并随每一行经过 Sort、Limit 和 Shuffle 全程搬运。即使最终只返回 N 行,读取、解压和传输的数据量仍与「参与计算的行数 × 列宽」成正比,大量资源消耗在最终会被丢弃的宽列上。
工作原理
LazyProjection(下文简称 LP)在 Sort、Limit、Shuffle 等算子之后插入延迟物化算子:
-
下层算子只携带
(小列, shard_id, row_locator),不再携带宽列,行宽显著变小。 -
顶层的 LP 算子拿到最终 N 行后,按这些行的
row_locator回到存储读取宽列,再拼接成完整的结果行。
由此,宽列的读取、解压和传输量从「参与计算的行数 × 列宽」降低到「N × 列宽」。
以下以日志表 logs 为例,查询按 ts 排序取前 100 条日志,其中 msg 为宽列:
SELECT ts, msg FROM logs WHERE level >= 4 ORDER BY ts LIMIT 100;
关闭 LP 时的执行计划(简化示意):
Limit
-> Sort -- 含宽列 msg,行宽大
Sort Key: ts
-> Seq Scan on logs -- 读取并解压 msg
Filter: (level >= 4)
开启 LP 时的执行计划(简化示意):
Lazy Project
Delayed Columns: msg -- 按最终 100 行的 row_locator 回表读取 msg
-> Limit
-> Sort -- 只含 ts、shard_id、row_locator,行宽小
Sort Key: ts
-> Seq Scan on logs -- 不读取 msg
Filter: (level >= 4)
其中,row_locator 是引擎内部使用的行定位信息,对上层算子不透明。LP 仅改变宽列的读取时机,不改变 SQL 语义,查询结果、行序和行数均与关闭 LP 时一致。
使用限制
-
仅 Hologres V5.0.0 及以上版本支持 LazyProjection。
-
仅面向输出行数少、且需要回传宽列的查询。窄列不会被延迟:同一数据源的可延迟列合计估算宽度不超过
hg_experimental_lazy_projection_min_delayed_column_width(默认 512 字节)时,不使用 LP。 -
仅在 Sort、Limit、Shuffle 等白名单算子组成的链路上生效。Join 方面,支持 Inner Hash Join、Cross Hash Join 以及 LEFT/RIGHT OUTER HASH JOIN,暂不支持 FULL OUTER JOIN 及其他 Outer Join 实现。
-
LP 仅为执行层的内部优化,不改变查询结果。
生效条件
LP 由优化器基于成本自动决定是否插入,无需改写 SQL。是否插入取决于以下几方面:
-
计划形态与位置:LP 插入在 Sort、Limit、Shuffle 等算子之后,且从 Scan 到 LP 插入点之间的算子须满足使用限制中的算子链路要求。
-
硬门槛:需同时满足以下条件。
-
LP 处(root)的输出行数不高于
hg_experimental_lazy_projection_max_output_rows(默认 2000,取值范围 0~10000),高于此值时不插入 LP。 -
同一数据源的可延迟列合计估算宽度严格大于
hg_experimental_lazy_projection_min_delayed_column_width(默认 512 字节)。
-
-
成本判断:满足硬门槛后,优化器汇总上游算子(主要是 Sort,其次是 Motion、Join)因行变窄而节省的成本,乘以
hg_experimental_lazy_projection_benefit_discount后,与 LP 自身的回表成本比较。收益与成本的比值须大于hg_experimental_lazy_projection_min_benefit_cost_ratio才会插入 LP。
一般规律:下层参与计算的行数越多、LIMIT 越小、延迟列越宽,越容易命中 LP。下层行数很少时,即使列很宽,节省的成本也可能不足以抵消回表成本,此时不会插入 LP。
开启与关闭
LP 默认开启。如需调整,可通过以下方式设置:
-
会话级开启或关闭:
-- 开启 SET hg_experimental_enable_lazy_projection = on; -- 关闭 SET hg_experimental_enable_lazy_projection = off; -
数据库级开启,对该数据库后续新建的连接生效,需要具备管理权限:
ALTER DATABASE <DATABASE_NAME> SET hg_experimental_enable_lazy_projection = on; -
恢复数据库级默认值:
ALTER DATABASE <DATABASE_NAME> RESET hg_experimental_enable_lazy_projection;
关闭后,查询完全退化到非 LP 的执行路径。已在执行中的查询不受影响,从下一条新查询起不再使用 LP。
参数说明
除总开关外,其余为成本模型参数,用于调优,一般保持默认值即可。
|
参数名 |
默认值 |
说明 |
|
|
on |
总开关。设置为 off 时,优化器不生成 LP 计划节点。 |
|
|
2000 |
LP 处(root)输出行数高于此阈值时不插入 LP。取值范围:0~10000。 |
|
|
512 |
同一数据源的可延迟列合计估算宽度须严格大于此值,才进入成本判断。单位:字节。 |
|
|
0.028 |
上游算子收益汇总后的缩放系数,缩放后再与 LP 自身成本比较。调小更保守,调大更激进。 |
|
|
0.00025 |
回表成本系数,即每个估算 source block 中每字节延迟列的成本。调大则回表成本更高,更难命中 LP。 |
|
|
28.6 |
输出成本系数,即每个估算 output block 中每字节输出行的成本。调大则更难命中 LP。 |
|
|
1.0 |
收益与成本的比值须大于此值才插入 LP。调小(如 0)更易命中,调大更保守。 |
确认功能是否生效
可通过以下方式确认查询是否使用了 LP。执行计划的查看方法请参见EXPLAIN和EXPLAIN ANALYZE。
-
查看 EXPLAIN 执行计划:计划顶层出现
Lazy Project节点,并带有Delayed Columns字段,列出被延迟读取的列名。涉及多个数据源时,可能嵌套多层Lazy Project节点。同时,下层 Sort、Limit 节点的 width 会显著变小。 -
查看 EXPLAIN ANALYZE 统计信息:
Lazy Project节点会输出以下累计统计字段,可据此推算每轮攒批数、每轮 RPC 数以及每行回表字节数。字段
说明
lp_rpc_count累计发出的回表 RPC 数。
lp_rows_seeked累计从存储取回的行数。
lp_input_batches累计进入主流程的子节点 batch 数。
lp_distinct_shards每个 batch 内涉及的 shard 数之和(累计值)。
-
对比 Read bytes:开启 LP 后,存储端 Scan 不再读取宽列,EXPLAIN ANALYZE 结果中的
Read bytes会显著下降。这是衡量 LP 收益最直接的指标。
调优建议
-
希望更多查询命中 LP:在评估影响的前提下,可提高
hg_experimental_lazy_projection_max_output_rows(不超过 10000),降低hg_experimental_lazy_projection_min_benefit_cost_ratio(如设置为 0),或降低hg_experimental_lazy_projection_output_cost_unit。SET hg_experimental_lazy_projection_min_benefit_cost_ratio = 0; -
希望更保守地使用 LP:可调低
hg_experimental_lazy_projection_benefit_discount,或调高hg_experimental_lazy_projection_min_benefit_cost_ratio、hg_experimental_lazy_projection_fetch_cost_unit。 -
临时关闭 LP:
SET hg_experimental_enable_lazy_projection = off;
使用示例
-
创建日志表,其中
msg为宽列。CREATE TABLE logs ( id int NOT NULL, ts int NOT NULL, level int NOT NULL, msg text NOT NULL ) WITH ( orientation = 'column', clustering_key = 'ts' ); -
执行 TopN 查询。
-- 默认已开启,可省略 SET hg_experimental_enable_lazy_projection = on; SELECT * FROM logs WHERE level >= 4 ORDER BY ts LIMIT 100; -
查看执行计划,确认是否命中 LP。
EXPLAIN SELECT * FROM logs WHERE level >= 4 ORDER BY ts LIMIT 100;执行计划中出现
Lazy Project(Delayed Columns: msg),且下层 Sort、Scan 节点的 width 显著变小,即表示命中 LP。说明是否命中 LP 取决于数据量与列宽。表中数据量较小,或
msg列的估算宽度不足时,可能不会触发 LP。