Dynamic Table查询改写

更新时间:
复制为 MD 格式

本文介绍 Hologres Dynamic Table 的查询改写能力、使用方式与限制。

查询改写介绍

在大数据/数仓场景中,明细表数据量通常很大(亿级、百亿级),而业务与分析查询又高度依赖在明细表上进行多维 GROUP BY + 聚合,例如:按天 / 小时 / 城市统计订单量、GMV;按渠道 / 终端统计 PV/UV、转化率等。如果每次都直接在明细表上做聚合,会遇到如下问题:

  • 聚合成本高:每次查询都从明细表全表或大范围扫描并聚合,消耗大量 CPU 和 IO;

  • 明细表压力大:影响同库内其他任务,或需要频繁扩容。

Hologres 提供 Dynamic Table + 查询改写能力:当某张基表上已经通过 Dynamic Table 做了预聚合,优化器可以在满足条件时,将用户写的“面向基表的聚合查询”自动改写为“对Dynamic Table 的查询”,从而跳过昂贵的聚合计算。主要收益有:

  • 减轻明细表聚合压力:高频指标(订单数、GMV、PV/UV 等)可从 Dynamic Table 直接读取聚合结果,减少对明细表的重复扫描和聚合。

  • 显著提升查询响应速度:报表、自助分析、交互式查询在命中 Dynamic Table 时,聚合计算大幅减少,延迟降低,使用体验接近“查宽表”。

  • 对上层使用无侵入:数据分析师、业务开发依然只需了解基表模型;Dynamic Table 的设计与维护由数仓/平台团队统一负责;性能优化对上层透明。

通过Hologres Dynamic Table查询改写,可以很好的支持如下场景:

  • 实时/准实时运营看板和监控;

  • 多维度 BI 分析、自助取数;

  • 核心指标体系(GMV、订单量、活跃用户数等)的统一口径加速。

使用与限制

  • 版本限制:仅Hologres 4.1及以上版本支持该功能

  • 查询一致性限制:查询改写基于 Dynamic Table 最近一次刷新结果,与基表最新状态相比,存在一定时间窗口的延迟,属于弱一致行为。

  • 基表类型限制:

    • 支持的基表类型有:Hologres内表、Paimon外表(Foreign Table方式创建)、MaxCompute外表(Foreign Table方式创建)

    • 如果基表是Hologres分区表,不支持基表为物理分区表。但基表可以是逻辑分区

    • 不支持External Table

  • Dynamic Table表类型限制:

    • 支持:非分区 Dynamic Table;逻辑分区的 Dynamic Table;

    • 不支持:物理分区的 Dynamic Table;External Dynamic Table。

  • Dynamic TableQuery 定义限制:

    • Query中仅支持单表,暂不支持多表

    • Query中不支持FILTER 子句的聚合(如 sum(x) FILTER (WHERE ...)

    • Query中不支持在 SELECT 中引入基于聚合结果的额外计算列(如 sum(x)/count(x)

开启与配置查询改写

使用建议:适合看板、监控、分析等可接受秒级~分钟级延迟的场景;强实时、严对账场景建议直接查基表或采用其他强一致方案。

开启查询改写

当发起对基表的查询时,需要在查询时,加上hg_enable_query_rewriteGUC参数,来控制该查询是否命中查询改写:

说明

不建议在 DB 级别开启,可能带来性能损失。

-- 开启查询改写(Session 级别)
SET hg_enable_query_rewrite = on;

-- DB 级别设置(不推荐)
ALTER DATABASE <db_name> SET hg_enable_query_rewrite = on;

为 Dynamic Table 开启查询改写

创建 Dynamic Table 时,通过属性 allowed_to_rewrite_query 控制该表是否参与查询改写;未设置时,默认不参与改写。

CREATE [ OR REPLACE ] DYNAMIC TABLE [ IF NOT EXISTS ] [<schema_name>.]<table_name> (
  [col_name],
  [col_name],
  [col_name]
)
[LOGICAL PARTITION BY LIST(<partition_key>)]
WITH (
  ...,
  allowed_to_rewrite_query = '[true | false]',
  ...
)
AS
<query>;

参数说明:

  • allowed_to_rewrite_query:标记此 Dynamic Table 是否允许作为“查询改写候选”;

    • 'true':允许被查询改写使用;

    • 'false':默认值,不参与查询改写;

使用建议:

  • 专门用于加速聚合查询的 DT:设置为 'true'

  • 定义复杂且当前规则无法利用的 DT:设置为 'false',减少优化器无效探索。

修改 Dynamic Table 查询改写属性

可通过 ALTER DYNAMIC TABLE ... SET 修改是否参与查询改写:

ALTER DYNAMIC TABLE [IF EXISTS] [<schema_name>.]<table_name>
SET (allowed_to_rewrite_query = '[true | false]');

控制查询改写命中的 Dynamic Table

存在多个 Dynamic Table 时,可在 Hint 中指定候选表集合,缩小优化器搜索范围并控制优先级。Hint 用法见 HINT

SELECT /*+HINT query_rewrite_candidates(<schema.dt_name1> <schema.dt_name2> ...) */
  ...
FROM ...;

GUC使用说明:

  • 若有多个 Dynamic Table,需使用空格分隔;

  • 可指定 schema。

使用示例:

-- 仅允许 dt_sales 参与查询改写
SELECT /*+HINT query_rewrite_candidates(dt_sales) */
  day, hour, min(amount), max(amount)
FROM base_sales_table
GROUP BY day, hour;

支持的功能介绍

当前版本支持基于单表聚合的查询改写,主要包括三种模式:

  • 聚合维度一致的透明改写;

  • 聚合上卷(Group By 维度上卷聚合);

  • 条件补偿的聚合上卷(带过滤条件的聚合上卷)。

聚合维度一致

适用条件

  • 查询中的 GROUP BY 维度与 Dynamic Table 定义中的 GROUP BY 维度完全一致;

  • 查询使用的聚合函数可以由Dynamic Table中已有的聚合结果列直接表示;

  • 聚合函数类型本身不限(包括 DISTINCT),只要Dynamic Table中已经存在对应结果列。

使用示例

--创建基表
CREATE TABLE base_sales_table(
    day text not null,
    hour int,
    amount int
);
--写入数据
INSERT INTO base_sales_table
VALUES  ('20250529', 12, 1),
        ('20250529', 12, 2),
        ('20250529', 12, 2),
        ('20250529', 13, 3),
        ('20250530', 13, 4),
        ('20250530', 14, 5),
        ('20250531', 14, 6);

-- Dynamic table
CREATE DYNAMIC TABLE dt_sales
WITH (
    freshness = '1 minutes',
    auto_refresh_mode='incremental',
    auto_refresh_enable='false',
    allowed_to_rewrite_query='true'
)
AS
SELECT
    day,
    hour,
    min(amount),
    max(amount),
    sum(amount),
    count(amount),
    count(*) as rows,
    count(1) as rows1,
    count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;


REFRESH TABLE dt_sales;

查询示例:维度一致时,执行计划中可见基表查询被改写为对 Dynamic Table 的查询。

-- 查询维度一致
EXPLAIN SELECT day, hour, min(amount), max(amount) FROM base_sales_table GROUP BY day, hour;

聚合维度一致时执行计划示意

聚合上卷

适用条件

  • Dynamic Table 的 GROUP BY 维度为查询 GROUP BY 维度的超集(即 DT 的维度包含查询维度);

  • 查询中的聚合函数可以通过对 DT 已有聚合结果列再聚合得到;

  • 支持的聚合函数:min, max, count, sum, avg

  • 不支持 DISTINCT 聚合上卷(但维度一致场景直接用 DT 中结果列除外)。

聚合函数映射

原始查询基表的聚合函数

Dynamic Table 中需存在的聚合列

改写后聚合函数

sum(x)

sum(x)

sum(sum)

count(x)

count(x)

sum(count)

min(x)

min(x)

min(min)

max(x)

max(x)

max(max)

avg(x)

sum(x)count(x)

sum(sum) / sum(count)

使用示例

--创建基表
CREATE TABLE base_sales_table(
    day text not null,
    hour int,
    amount int
);
--写入数据
INSERT INTO base_sales_table
VALUES  ('20250529', 12, 1),
        ('20250529', 12, 2),
        ('20250529', 12, 2),
        ('20250529', 13, 3),
        ('20250530', 13, 4),
        ('20250530', 14, 5),
        ('20250531', 14, 6);

-- Dynamic table:为了验证查询改写的效果,先手动关闭自动刷新
CREATE DYNAMIC TABLE dt_sales
WITH (
    freshness = '1 minutes',
    auto_refresh_mode='incremental',
    auto_refresh_enable='false',
    allowed_to_rewrite_query='true'
)
AS
SELECT
    day,
    hour,
    min(amount),
    max(amount),
    sum(amount),
    count(amount),
    count(*) as rows,
    count(1) as rows1,
    count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;

--手动刷新dynamic table
REFRESH TABLE dt_sales;

查询示例 1:按 day 聚合(上卷)。查询基表时,GROUP BY 列为 Dynamic Table 定义中的子集,可被正常改写。

-- 原始查询
EXPLAIN SELECT day, min(amount), max(amount)
FROM base_sales_table
GROUP BY day;

聚合上卷按day执行计划示意

查询示例 2sum + count + avg 上卷。基表查询使用 avg,Dynamic Table 中有 sumcount,可推导出 avg,因此可被改写。

-- 原始查询
EXPLAIN SELECT day, sum(amount), count(amount), avg(amount)
FROM base_sales_table
GROUP BY day;

sum+count+avg上卷执行计划示意

条件补偿的聚合上卷(带过滤条件)

适用条件

  • 查询基表时包含 WHERE 过滤条件,但是Dynamic Table定义中不能有where过滤条件

  • 所有用于where过滤的字段都需要出现在 Dynamic Table 的 GROUP BY 维度中;

  • 聚合函数仅支持 min, max, count, sum, avg,不支持 DISTINCT。

使用示例:

--创建基表
CREATE TABLE base_sales_table(
    day text not null,
    hour int,
    amount int
);
--写入数据
INSERT INTO base_sales_table
VALUES  ('20250529', 12, 1),
        ('20250529', 12, 2),
        ('20250529', 12, 2),
        ('20250529', 13, 3),
        ('20250530', 13, 4),
        ('20250530', 14, 5),
        ('20250531', 14, 6);

-- Dynamic table:为了验证查询改写的效果,先手动关闭自动刷新
CREATE DYNAMIC TABLE dt_sales
WITH (
    freshness = '1 minutes',
    auto_refresh_mode='incremental',
    auto_refresh_enable='false',
    allowed_to_rewrite_query='true'
)
AS
SELECT
    day,
    hour,
    min(amount),
    max(amount),
    sum(amount),
    count(amount),
    count(*) as rows,
    count(1) as rows1,
    count(distinct amount) as cd
FROM base_sales_table
GROUP BY day, hour;

--手动刷新dynamic table
REFRESH TABLE dt_sales;

以下示例中,查询基表时带有 WHERE 条件,且过滤字段均出现在 GROUP BY 中,因此可被改写。

EXPLAIN SELECT day, sum(amount), count(amount), avg(amount)
FROM base_sales_table
WHERE day > '20250528' AND day <= '20250531'
GROUP BY day;

条件补偿聚合上卷执行计划示意

查看查询改写情况

开启查询改写后,可通过以下方式确认当前查询是否命中了 Dynamic Table。

  1. 通过执行计划查看:在 EXPLAIN 结果中查看 Scan 算子扫描的表名,可判断是否命中了 Dynamic Table。

  2. 通过慢 query 日志查看改写情况:在慢 query 日志 hologres.hg_query_log 表的 extended_info 字段中会记录查询改写命中的表;若改写失败,会包含相关错误说明。

select extended_info::json->>'rewrite_query_info' from hologres.hg_query_log where query_id = 'xxxxx';

                                                                                                               ?column?                                                                                                               
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 {"rewrite_failed_dt": [[\"public.dt3\", {\"rewrite_failed_cause\": \"Doesn't include all query required output columns\"}]], \"rewrite_succeeded_and_selected_dt\": [\"public.dt2\"], \"rewrite_succeeded_but_not_selected_dt\": [\"public.dt1\"]}
(1 row)

使用示例

示例 1:基表为 Hologres 内表的查询改写

基表来自 TPC-H 数据集 lineitem 100G,建表与导入方法见 一键导入公共数据集。本示例中 Dynamic Table 为增量刷新、非分区表。

CREATE DYNAMIC TABLE dt_lineitem_100g_incremental
WITH (
    freshness = '10 minutes',
    auto_refresh_mode='incremental',
    auto_refresh_enable='false',
    allowed_to_rewrite_query='true')
AS 
select
        l_returnflag,
        l_linestatus,
        l_shipdate,
        sum(l_quantity) as sum_qty,
        count(*) as count_order
from
        hologres_dataset_tpch_100g.lineitem
group by
        l_returnflag,
        l_linestatus,
        l_shipdate

--手动刷新
REFRESH DYNAMIC TABLE dt_lineitem_100g_incremental;

查询基表:

set hg_enable_query_rewrite = on;
explain 
select
        l_returnflag,
        l_linestatus,
        l_shipdate,
        sum(l_quantity) as sum_qty,
        count(*) as count_order
from
        hologres_dataset_tpch_100g.lineitem
where  l_shipdate =  '1998-12-01' 
group by
        l_returnflag,
        l_linestatus,
        l_shipdate

执行计划中可以看到查询被改写为查询DT:

示例1查询改写为DT执行计划示意

查询基表的结果如下:

 l_returnflag | l_linestatus | l_shipdate | sum_qty  | count_order 
--------------+--------------+------------+----------+-------------
 N            | O            | 1998-12-01 | 52841.00 |        2070
(1 row)

直接查询 Dynamic Table(本示例关闭了自动刷新,仅做了一次手动刷新),结果与最近一次刷新一致:

select
        l_returnflag,
        l_linestatus,
        l_shipdate,
        sum_qty,
        count_order
from
        dt_lineitem_100g_incremental
where  l_shipdate =  '1998-12-01' ;

 l_returnflag | l_linestatus | l_shipdate | sum_qty  | count_order 
--------------+--------------+------------+----------+-------------
 N            | O            | 1998-12-01 | 52841.00 |        2070
(1 row)

示例 2:基表是 Paimon 外表的查询改写

当基表为 Paimon 外表时,同样可命中查询改写。示例步骤如下:

  1. 准备 Paimon 表:示例在 Paimon 中导入 TPC-H customer 100G 表,导入方法见 Paimon Table

  2. 在 Hologres 中创建 Paimon 外表:需使用 Foreign Table 方式创建,详见 基于DLF访问Paimon Catalog

-- 创建 foreign server
CREATE SERVER IF NOT EXISTS paimon_server FOREIGN DATA WRAPPER dlf_fdw OPTIONS (
    catalog_type 'paimon',
    metastore_type 'dlf-rest', 
    dlf_catalog '<dlf_catalog_name>'
);

-- 使用IMPORT FOREIGN SCHEMA创建paimon外表
IMPORT FOREIGN SCHEMA <schema_name>
limit to (customer) 
FROM SERVER paimon_server into public
options (if_table_exist 'update');

--查询数据
SELECT * FROM customer

创建 Dynamic Table 增量消费 Paimon 外表:在 Hologres 中创建 Dynamic Table,以增量刷新方式消费 Paimon 外表;为便于验证改写效果,本示例关闭自动刷新。

--创建dynamic table
CREATE DYNAMIC TABLE dt_paimon_customer
WITH (
    freshness = '10 minutes',
    auto_refresh_mode='incremental',
    auto_refresh_enable='false',
    allowed_to_rewrite_query='true')
AS
SELECT
     c_custkey,
     avg(c_acctbal) ,
     sum(c_acctbal) ,
     count(c_acctbal)
FROM customer
group by c_custkey;

--手动刷新dynamic table
REFRESH DYNAMIC TABLE dt_paimon_customer;

4、查询 Paimon 外表并开启查询改写。

set hg_enable_query_rewrite = on;

SELECT
     c_custkey,
     avg(c_acctbal) ,
     sum(c_acctbal) ,
     count(c_acctbal)
FROM
   customer
group by c_custkey ORDER BY 3 DESC  LIMIT 3;

c_custkey	| avg	        |sum	    |count
----------|-------------|---------|-----

3605586	  |9999.990000  |	9999.99	|1
10705496	|9999.990000	|9999.99	|1
14959900	|9999.990000	|9999.99	|1

查询 Dynamic Table:结果为最近一次刷新后的数据。

SELECT * FROM dt_paimon_customer ORDER BY 3 DESC  LIMIT 3;

c_custkey	| avg	        |sum	    |count
----------|-------------|---------|-----
3605586	  |9999.990000  |	9999.99	|1
10705496	|9999.990000	|9999.99	|1
14959900	|9999.990000	|9999.99	|1

通过执行计划确认:可看到查询已改写为访问 Dynamic Table。

示例2查询改写为DT执行计划示意