Hologres自V5.0版本起支持镜像表,基于Dynamic Table将源外表的指定列持续刷新到Hologres本地,以加速External Database下外表的查询。业务侧仍然查询源外表,由优化器自动选择是否使用镜像表加速。
功能简介
镜像表用于解决查询湖上外表时的性能瓶颈。外表的数据与索引都在湖上,Hologres无法为其构建本地索引,向量检索、全文检索以及高频过滤聚合等场景难以获得低延迟响应。开启镜像表后,源外表中查询所需的列会按指定的刷新间隔同步到Hologres本地,复用Hologres的分布键、向量索引与全文倒排索引能力;同时,未镜像的大列仍然留在湖上,仅在需要时回表读取,从而在性能与存储成本之间取得平衡。
镜像表对业务查询是透明的:您仍然按原有SQL查询源外表,优化器会自动判断能否使用镜像表加速,无法安全改写时自动回退到源外表。
镜像表与湖表镜像是两套相互独立的加速机制,语法、表属性和观测函数均不相同,请勿混用。湖表镜像自Hologres V3.2版本起支持,通过ALTER EXTERNAL TABLE为外表整表或指定分区开启元数据与数据镜像;镜像表自Hologres V5.0版本起支持,通过CREATE DYNAMIC TABLE ... MIRRORS按列构建本地Dynamic Table,并可复用向量索引、全文倒排索引与回表查询能力。湖表镜像的详细介绍,请参见湖表镜像。
典型场景
向量与全文检索场景
-
将向量列和常用标量过滤列同步到Hologres,复用Hologres向量索引完成低延迟TopK检索。
-
将文本、
BLOB等大列保留在湖上,仅在TopK命中少量记录后回表读取,降低本地存储成本。 -
在镜像表上创建全文倒排索引,加速被镜像文本列上的全文检索。
标量场景
将查询所需列同步到Hologres,复用本地存储、分布和索引能力,加速过滤、聚合、排序和关联等通用查询。
完整的建表和检索示例,请参见最佳实践“基于镜像表实现Paimon湖表的全文+向量混合检索”。
执行路径
优化器会根据查询形态、镜像表覆盖列、索引和代价,自动在以下三条执行路径中选择一条:
-
覆盖改写:查询需要的列全部位于镜像表中,直接扫描镜像表。
-
回表改写:先在镜像表中完成受支持的
ORDER BY ... LIMITTopK(向量场景可利用索引),再通过Lazy Project按行标识回表读取源外表,补齐未镜像的列。 -
未改写:查询不满足改写条件、您已主动关闭改写,或源表扫描代价更低时,继续扫描源外表。
功能特点
-
对查询透明:业务SQL查询源外表,不需要显式改查镜像表。
-
复用内表能力:镜像表是Hologres本地表,可以使用分布键、向量索引、全文倒排索引等能力。
-
按需回表:未镜像的大列可以在受支持的TopK截断后从源外表补取。
-
快照一致性:单次回表查询会对齐镜像表快照与源外表快照。
-
可降级:无法安全改写时自动回退到源外表;也可以通过GUC或表属性主动关闭改写。
镜像表的创建
创建限制
-
源表必须是External Database下的外表。当前不支持以普通Hologres内表、视图或物化视图作为源表。
-
Hologres V5.0版本暂不支持分区源表的镜像加速能力。
-
一张源表只能关联一张镜像表。若需要调整镜像列或索引,请先删除已有镜像表再重新创建。
-
镜像表与使用它的查询必须位于同一个Hologres Database,优化器不会跨Database查找镜像表。
-
freshness为必填属性,用于定义Dynamic Table的目标刷新间隔。 -
使用镜像表加速查询的用户需要同时拥有源外表和镜像表的
SELECT权限。没有镜像表权限时,查询回退到源外表,不会因改写失败而报错。
创建语法
CREATE DYNAMIC TABLE [IF NOT EXISTS] <mirror_table>
[(<table_constraint> [, <table_constraint> ...])]
MIRRORS <source_table> (<expr1>, <expr2>, ...)
[WHERE <filter_condition>]
WITH (
<property_name> = <value>,
...
);
主要参数说明如下。
|
参数 |
是否必填 |
说明 |
|
|
是 |
镜像表名称,可以包含Schema名。 |
|
|
否 |
表级约束,支持 |
|
|
是 |
指定External Database下的源外表。 |
|
|
是 |
指定同步到镜像表的列或表达式,至少包含一项。 |
|
|
否 |
创建过滤型镜像表。当前优化器不支持使用带 |
|
|
是 |
支持Dynamic Table的通用属性,以及镜像表专属属性。 |
镜像表沿用Dynamic Table支持的刷新、存储和索引属性,并额外提供镜像表专属属性。常用属性如下。
|
属性类型 |
属性 |
默认值 |
说明 |
|
Dynamic Table通用属性 |
|
无 |
必填,目标刷新间隔,例如 |
|
Dynamic Table通用属性 |
|
|
自动模式先尝试增量刷新,不满足条件时回退为全量刷新;也可以显式设置为 |
|
Dynamic Table通用属性 |
|
|
是否自动刷新。关闭后可使用 |
|
Table通用属性 |
|
无 |
镜像表分布键,建议选择常用过滤或关联列。 |
|
Table通用属性 |
|
无 |
向量索引配置。 |
|
镜像表专属属性 |
|
|
是否允许优化器使用该镜像表改写源表查询。 |
其他Dynamic Table属性的含义和取值规则与普通Dynamic Table相同,详情请参见CREATE DYNAMIC TABLE。
以下示例将源外表的id和embedding两列同步到镜像表。
CREATE DYNAMIC TABLE public.source_table_mirror
MIRRORS paimon_ext_db.ext_schema.source_table (id, embedding)
WITH (
freshness = '1 minute',
auto_refresh_mode = 'incremental'
);
查询改写
镜像表的查询改写由会话级GUC总开关控制,默认开启。关闭后,所有查询直接读取源外表。
-- 默认开启
SET hg_enable_mirror_table_rewrite = on;
-- 关闭改写,所有查询直接读取源外表
SET hg_enable_mirror_table_rewrite = off;
即使改写开关开启,优化器仍会根据语义安全性和代价决定是否使用镜像表,因此“存在镜像表”不等于“每条查询都一定使用镜像表”。
Dynamic Table通用的查询改写机制,请参见Dynamic Table查询改写。
镜像表覆盖情况下的查询改写
当查询所引用的源外表列都能由镜像表提供时,优化器可以将源外表扫描替换为镜像表扫描。过滤条件、排序、聚合、窗口、集合操作或Join中引用的源外表列,也必须被镜像表覆盖或能够由镜像表达式安全映射。
EXPLAIN
SELECT id, embedding
FROM paimon_ext_db.ext_schema.source_table
WHERE id > 0
ORDER BY id;
改写成功时,执行计划中会出现对source_table_mirror的Seq Scan或Index Scan。
覆盖改写的限制如下。
-
镜像定义中的普通列引用可以参与改写。
-
镜像定义可以包含仅依赖单个源列的确定性表达式,例如
col1 + 2;查询必须使用可与该表达式等价匹配的写法。 -
镜像定义不支持使用多个源列共同生成一个镜像列,例如
col1 + col2。 -
带
WHERE定义的镜像表当前不能参与查询改写。 -
设置
allowed_to_rewrite_query = false、会话关闭hg_enable_mirror_table_rewrite,或用户没有镜像表SELECT权限时,都会回退到源外表。 -
改写后的镜像表计划仍会与源表计划进行代价比较,源表计划代价更低时可能不选择镜像表。
建议镜像表定义优先使用直接列引用,并将过滤条件保留在业务查询中,以获得最稳定的改写覆盖范围。
回表查询
Hologres V5.0版本的回表改写要求查询包含受支持的ORDER BY ... LIMIT TopK。当镜像表包含TopK排序、检索和过滤所需列,但不包含最终输出的部分列时,优化器可以生成“镜像表扫描 + Lazy Project回表”计划。仅有过滤条件并投影未镜像列的查询不会回表,而会读取源外表。
以下示例使用镜像表中的embedding列完成向量TopK,仅在得到前5条记录后回表读取content列。
EXPLAIN
SELECT
id,
content,
approx_cosine_distance(
embedding,
'{0.1,0.2,0.3,0.4}'
) AS distance
FROM paimon_ext_db.ext_schema.source_table
ORDER BY distance DESC
LIMIT 5;
回表改写成功时,计划中会同时出现镜像表扫描、VectorCond => KNN、Lazy Project和Delayed Columns: content。
回表查询的限制如下。
-
当前回表源仅支持Paimon非分区表,并要求源表设置
"data-evolution.enabled" = 'true'和"row-tracking.enabled" = 'true'。 -
查询必须包含优化器支持的
ORDER BY ... LIMITTopK,TopK的排序列或排序表达式必须由镜像表覆盖,TopK前需要执行的过滤条件只能引用镜像表已覆盖的列。 -
优化器会比较“源外表扫描”和“镜像表索引扫描 + 回表”的代价,预计回表行数过多时可能选择源外表。
Hologres V5.0版本暂不支持单独关闭回表改写。若需确保查询不发生回表,需关闭该镜像表的全部查询改写,此时覆盖改写的加速效果也会一并失效。关闭方式请参见常用运维操作。
一致性保证
镜像表异步刷新,因此源外表最新数据与镜像表最近完成刷新数据之间可能存在时间差。镜像表查询的数据可见性以最近一次成功刷新所对应的源表快照为准。
单次查询的一致性
-
覆盖改写时,查询读取镜像表最近可用的正式快照。
-
回表改写时,系统会为本次查询固定镜像表快照,从镜像表快照中取得其对应的源外表Snapshot ID,并按该Snapshot ID回表。
因此,同一条SQL中镜像表命中的行和回表补取的列来自同一个逻辑版本,避免出现用旧索引命中行却读取到新版本内容的情况。
改写查询与非改写查询的一致性
默认情况下,走镜像表改写的查询读取镜像表最近完成刷新对应的快照,不走镜像表改写的外表查询读取源外表最新快照。因此在镜像表尚未追平源表时,两类查询可能看到不同的数据量。例如源表已有9行,而镜像表只刷新到包含8行的快照,此时走镜像表改写的查询看到8行,不走镜像表改写的查询看到9行。
若业务要求不走镜像表的查询也尽量与镜像表保持相同可见版本,可开启hg_enable_external_table_visible_snapshot_consistent。开启后一致性优先于读取最新数据,不走镜像表改写的外表查询也会读到镜像表当前快照。
SET hg_enable_external_table_visible_snapshot_consistent = on;
运维与观测
查看刷新进度
通过如下SQL查看镜像表当前的快照信息。函数入参为镜像表的表标识(regclass),建议带上Schema名以避免歧义。
SELECT *
FROM hologres.hg_get_mirror_table_snapshot_info(
'public.source_table_mirror'::regclass
);
返回字段说明如下。
|
字段 |
类型 |
说明 |
|
|
|
镜像表内部快照名称。 |
|
|
|
源外表名称。 |
|
|
|
该镜像快照对应的源表Snapshot ID。 |
|
|
|
源表快照提交时间。 |
可与Paimon源表的快照列表交叉检查,确认镜像表的推进情况。
SELECT snapshot_id, commit_time
FROM hologres.hg_list_snapshots(
'paimon_ext_db.ext_schema.source_table'
)
ORDER BY commit_time;
源表产生新快照后执行REFRESH TABLE,source_snapshot_id和source_snapshot_time应推进到新的源表快照。
REFRESH TABLE public.source_table_mirror;
SELECT snapshot_name, source_snapshot_id, source_snapshot_time
FROM hologres.hg_get_mirror_table_snapshot_info(
'public.source_table_mirror'::regclass
);
刷新失败时,镜像表不会推进快照,查询仍可能读取最近一次成功刷新生成的数据。请结合Dynamic Table刷新任务状态和日志排查失败原因,详情请参见运维Dynamic Table刷新任务。
查看查询是否使用镜像表
通过EXPLAIN查看查询的执行路径。
EXPLAIN
SELECT id, embedding
FROM paimon_ext_db.ext_schema.source_table
WHERE id > 0;
各执行路径对应的计划标志如下。
|
执行路径 |
计划标志 |
|
覆盖改写 |
|
|
回表改写 |
|
|
未改写 |
|
|
向量索引下推 |
|
|
全文倒排索引下推 |
|
使用EXPLAIN ANALYZE可以进一步确认实际执行路径上镜像表使用的Snapshot。
常用运维操作
镜像表沿用Dynamic Table的运维方式,常用操作如下。
-- 手动刷新
REFRESH TABLE public.source_table_mirror;
-- 暂停自动改写
ALTER DYNAMIC TABLE public.source_table_mirror
SET (allowed_to_rewrite_query = 'false');
-- 恢复自动改写
ALTER DYNAMIC TABLE public.source_table_mirror
SET (allowed_to_rewrite_query = 'true');
-- 删除镜像表
DROP TABLE IF EXISTS public.source_table_mirror;
完整使用示例
本示例的前置假设如下。
-
已存在非分区Paimon外表
paimon_ext_db.ext_schema.source_table。 -
源表包含
id、content和四维float4[]向量列embedding。 -
Paimon源表已开启Data Evolution和Row Tracking。
-
当前用户对源外表和镜像表均有
SELECT权限。
步骤一:创建镜像表
只同步id和embedding两列,大字段content留在源表。创建时先关闭自动刷新和查询改写,便于逐步验证。
CREATE DYNAMIC TABLE public.source_table_mirror
(CHECK(array_ndims(embedding) = 1
AND array_length(embedding, 1) = 4))
MIRRORS paimon_ext_db.ext_schema.source_table (id, embedding)
WITH (
freshness = '1 minute',
auto_refresh_mode = 'incremental',
auto_refresh_enable = 'false',
allowed_to_rewrite_query = 'false',
vectors = '{
"embedding": {
"algorithm": "HGraph",
"distance_method": "Cosine",
"builder_params": {
"base_quantization_type": "sq8_uniform"
}
}
}'
);
步骤二:首次刷新与状态检查
REFRESH TABLE public.source_table_mirror;
SELECT count(*)
FROM public.source_table_mirror;
SELECT snapshot_name, source_table_name,
source_snapshot_id, source_snapshot_time
FROM hologres.hg_get_mirror_table_snapshot_info(
'public.source_table_mirror'::regclass
);
确认镜像表数据量符合预期,且快照函数返回记录后,再继续开启自动刷新。
步骤三:开启并验证自动刷新
ALTER DYNAMIC TABLE public.source_table_mirror
SET (auto_refresh_enable = 'true');
查看镜像表的刷新历史。
SELECT
refresh_type,
refresh_mode,
status,
refresh_start,
refresh_end,
message
FROM hologres.hg_dynamic_table_refresh_history
WHERE datname = current_database()
AND schema_name = 'public'
AND dynamic_table_name = 'source_table_mirror'
ORDER BY refresh_start DESC
LIMIT 10;
确认最近的自动刷新记录中refresh_type为auto、status为SUCCESS,且刷新时间和模式符合预期后,再开启查询改写。
步骤四:开启查询改写
SET hg_enable_mirror_table_rewrite = on;
ALTER DYNAMIC TABLE public.source_table_mirror
SET (allowed_to_rewrite_query = 'true');
步骤五:验证覆盖改写
EXPLAIN
SELECT id, embedding
FROM paimon_ext_db.ext_schema.source_table
WHERE id > 0;
改写成功时,计划中会扫描source_table_mirror,而不是源外表。
步骤六:验证向量TopK回表
EXPLAIN
SELECT
id,
content,
approx_cosine_distance(
embedding,
'{0.1,0.2,0.3,0.4}'
) AS distance
FROM paimon_ext_db.ext_schema.source_table
ORDER BY distance DESC
LIMIT 5;
回表改写成功时,计划应同时包含以下标志。
-
扫描
source_table_mirror。 -
VectorCond => KNN,表示向量TopK已下推。 -
Lazy Project和Delayed Columns: content,表示content列从源外表补取。