分区范围投影 (Partition Range Projections)¶
作者:JiangChong | 发布时间:2026-06-09
OpenText™ Analytics Database 从 11.0.x 版本开始支持指定分区键范围的投影。
默认情况下,投影会存储分区表数据的所有行。随着时间的推移,这一要求可能会带来不断增加的开销:
- 随着数据积累,需要越来越多的存储空间来存储那些很少被查询(甚至从未被查询)的大量数据。
- 大型投影可能会阻碍优化措施,例如更好的编码方式,或者更改投影的排序顺序或分段方式。像这样的投影 DDL 更改需要您刷新整个投影。根据投影的大小,此刷新操作可能需要数小时甚至数天。
您可以通过为分区表创建指定相对狭窄分区键范围的投影来最小化这些问题。例如,表 store_orders 按 order_date 进行分区,如下所示:
=> CREATE TABLE public.store_orders(
order_no int,
order_date timestamp NOT NULL,
shipper varchar(20),
ship_date date
);
CREATE TABLE
=> ALTER TABLE store_orders
PARTITION BY order_date::DATE
GROUP BY date_trunc('month', (order_date)::DATE);
ALTER TABLE
如果需要,您可以创建 store_orders 的一个投影,指定表分区键的连续范围。在以下示例中,投影 ytd_orders 指定仅包含从年初第一天起的订单:
=> CREATE PROJECTION ytd_orders AS
SELECT * FROM store_orders
ORDER BY order_date
ON PARTITION RANGE BETWEEN date_trunc('year',now())::date AND NULL;
WARNING 4468: Projection <public.ytd_orders_b0> is not available for query processing. Execute the select start_refresh() function to copy data into this projection.
The projection must have a sufficient number of buddy projections and all nodes must be up before starting a refresh
WARNING 4468: Projection <public.ytd_orders_b1> is not available for query processing. Execute the select start_refresh() function to copy data into this projection.
The projection must have a sufficient number of buddy projections and all nodes must be up before starting a refresh
CREATE PROJECTION
=> SELECT refresh();
refresh
---------------------------------------------------------------------------------------
Refresh completed with the following outcomes:
Projection Name: [Anchor Table] [Status] [Refresh Method] [Error Count] [Duration (sec)]
----------------------------------------------------------------------------------------
"public"."ytd_orders_b1": [store_orders] [refreshed] [scratch] [0] [0]
"public"."ytd_orders_b0": [store_orders] [refreshed] [scratch] [0] [0]
(1 row)
每个 ytd_orders 伙伴投影 (buddy projection) 每个节点只需要 7 个 ROS 容器,而锚表 (anchor table) 的超级投影 (superprojection) 需要 77 个容器:
=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name
FROM PARTITIONS
WHERE projection_name ilike 'store_orders_b%'
GROUP BY node_name, projection_name
ORDER BY node_name;
NumROS | projection_name | node_name
--------+-----------------+------------------
77 | store_orders_b0 | v_vmart_node0001
77 | store_orders_b1 | v_vmart_node0001
77 | store_orders_b0 | v_vmart_node0002
77 | store_orders_b1 | v_vmart_node0002
77 | store_orders_b0 | v_vmart_node0003
77 | store_orders_b1 | v_vmart_node0003
(6 rows)
=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name
FROM PARTITIONS
WHERE projection_name ilike 'ytd_orders%'
GROUP BY node_name, projection_name
ORDER BY node_name;
NumROS | projection_name | node_name
--------+-----------------+-----------------+
7 | ytd_orders_b0 | v_vmart_node0001
7 | ytd_orders_b1 | v_vmart_node0001
7 | ytd_orders_b0 | v_vmart_node0002
7 | ytd_orders_b1 | v_vmart_node0002
7 | ytd_orders_b0 | v_vmart_node0003
7 | ytd_orders_b1 | v_vmart_node0003
(6 rows)
1. 分区范围要求 (Partition range requirements)¶
分区范围表达式必须符合适用于表级分区的要求——例如分区键格式和数据类型验证。
以下要求和约束专门适用于分区范围投影:
- 锚表必须已经分区。
- 分区范围表达式必须与表的分区表达式兼容。
- 第一个范围表达式解析出的分区键必须小于或等于第二个表达式。
- 如果投影是未分段的 (unsegmented),则锚表的至少一个超级投影也必须是未分段的。否则,数据库会将该投影添加到数据库目录中,但会发出警告,指出在您创建未分段的超级投影之前,此投影不能用于处理查询。
- 分区范围表达式不支持子查询。
2. 锚表依赖关系 (Anchor table dependencies)¶
如前所述,分区范围投影依赖于锚表在相同的表达式上进行分区。如果您从投影的锚表中移除表分区,数据库将删除依赖的投影。同样,如果您修改锚表的分区子句,数据库也将删除该投影。
以下情况例外:如果锚表的新分区子句保持分区表达式不变,则依赖的投影不会被删除,并且仍可用于查询。例如,表 store_orders 及其投影 ytd_orders 最初分区如下:
=> ALTER TABLE store_orders
PARTITION BY order_date::DATE
GROUP BY DATE_TRUNC('month', (order_date)::DATE);
...
=> CREATE PROJECTION ytd_orders AS
SELECT * FROM store_orders
ORDER BY order_date
ON PARTITION RANGE BETWEEN date_trunc('year',now())::date AND NULL;
如果您现在修改 store_orders 以使用分层分区 (hierarchical partitioning),数据库将重新分区表数据及其分区范围投影:
=> ALTER TABLE store_orders
PARTITION BY order_date::DATE
GROUP BY CALENDAR_HIERARCHY_DAY(order_date::DATE, 2, 2)
REORGANIZE;
NOTICE 4785: Started background repartition table task
ALTER TABLE
由于 store_orders 和 ytd_orders 投影仍然按 order_date 列分区,因此 ytd_orders 投影保持有效。此外,投影数据的范围保持不变,因此投影不需要刷新。但是,在后台,元组移动器 (Tuple Mover) 会根据其锚表的新分层分区静默地重新组织投影 ROS 容器:
=> SELECT COUNT (DISTINCT ros_id) NumROS, projection_name, node_name
FROM PARTITIONS
WHERE projection_name ilike 'ytd_orders%'
GROUP BY node_name, projection_name
ORDER BY node_name;
NumROS | projection_name | node_name
--------+-----------------+------------------
38 | ytd_orders_b0 | v_vmart_node0001
38 | ytd_orders_b1 | v_vmart_node0001
38 | ytd_orders_b0 | v_vmart_node0002
38 | ytd_orders_b1 | v_vmart_node0002
38 | ytd_orders_b0 | v_vmart_node0003
38 | ytd_orders_b1 | v_vmart_node0003
(6 rows)
3. 修改现有投影 (Modifying existing projections)¶
您可以使用 ALTER PROJECTION 修改投影的分区范围。如果新范围在旧范围内,则不需要刷新。否则,在投影反映修改后的分区范围之前需要刷新。在刷新之前,投影继续返回未修改范围内的数据。
例如,投影 ytd_orders 先前指定的分区范围从当前年的第一天开始。以下 ALTER PROJECTION 语句将范围更改为从去年的 10 月 1 日开始。新范围早于旧范围,因此数据库发出警告,要求刷新指定的投影 ytd_orders_b0 及其伙伴投影 ytd_orders_b1:
=> ALTER PROJECTION ytd_orders_b0
ON PARTITION RANGE BETWEEN
add_months(date_trunc('year',now())::date, -3) AND NULL;
WARNING 10001: Projection "public.ytd_orders_b0" changed to out-of-date state as new partition range is not covered by existing partition range
HINT: Call refresh() or start_refresh() to refresh the projections
WARNING 10001: Projection "public.ytd_orders_b1" changed to out-of-date state as new partition range is not covered by existing partition range
HINT: Call refresh() or start_refresh() to refresh the projections
ALTER PROJECTION
只要不会发生历史记录丢失或数据丢失,您就可以将普通投影更改为分区范围投影,例如执行以下操作:
4. 动态分区范围 (Dynamic partition ranges)¶
投影的分区范围可以是静态的(由始终解析为相同值的表达式设置),也可以是动态的。
例如,以下投影指定了 2021 年 6 月 1 日至 6 月 30 日之间的静态范围:
=> CREATE PROJECTION last_month_orders AS
SELECT * FROM store_orders
ORDER BY order_date
ON PARTITION RANGE BETWEEN '2021-06-01' AND '2021-06-30';
...
CREATE PROJECTION
更典型的情况是,分区范围表达式使用稳定的日期函数,如 ADD_MONTHS、DATE_TRUNC 和 NOW 来指定动态范围。在以下示例中,分区范围设置为从上个月的第一天开始。随着日历日期进入下一个月,分区范围也随之推进:
=> ALTER PROJECTION last_month_orders_b0
ON PARTITION RANGE BETWEEN
add_months(date_trunc('month', now())::date, -1) AND NULL;
ALTER PROJECTION
最佳实践: 始终通过将最大值设置为 NULL 来保持最大范围开放,并依靠查询来确定要获取的最大数据量。例如,获取上个月所有商店订单的查询可能如下所示:
=> SELECT * from store_orders
WHERE order_date BETWEEN
add_months(date_trunc('month', now())::date, -1) AND
add_months(date_trunc('month', now())::date + dayofmonth(now()), -1);
生成的执行计划显示它使用了分区范围投影 last_month_orders:
=> EXPLAIN SELECT * from store_orders
WHERE order_date BETWEEN
add_months(date_trunc('month', now())::date, -1) AND
add_months(date_trunc('month', now())::date + dayofmonth(now()), -1);
Access Path:
+-STORAGE ACCESS for store_orders [Cost: 34, Rows: 763 (NO STATISTICS)] (PATH ID: 1)
| Projection: public.last_month_orders_b0
| Materialize: store_orders.order_date, store_orders.order_no, store_orders.shipper, store_orders.ship_date
| Filter: ((store_orders.order_date >= '2021-06-01 00:00:00'::timestamp(0)) AND (store_orders.order_date <= '2021-06-30 00:00:00'::timestamp(0)))
| Execute on: All Nodes
5. 动态分区范围维护 (Dynamic partition range maintenance)¶
投影维护器 (Projection Maintainer) 是一项后台服务,每小时检查具有投影范围表达式的投影。如果投影中任一表达式的值发生变化,投影维护器会比较 PARTITION_RANGE_MIN 和 PARTITION_RANGE_MAX 中的新旧值,以确定分区范围是收缩还是扩展:
- 如果分区范围在任一方向上收缩(即
PARTITION_RANGE_MIN变大,或PARTITION_RANGE_MAX变小): - 投影维护器会使用新值更新系统表
PROJECTIONS中的PARTITION_RANGE_MIN和PARTITION_RANGE_MAX列。 -
排队一个 MERGEOUT 请求以清除该范围内未使用的数据。投影仍然可用于执行更新后范围内的查询。
-
如果分区范围在任一方向上扩展(即
PARTITION_RANGE_MIN变小,或PARTITION_RANGE_MAX变大): - 投影维护器将保持投影和
PROJECTIONS表不变。因为分区范围被视为未变(数据库认为现有投影数据是最新的),所以它永远无法被刷新以包含新扩展的数据。
示例: 以下投影创建了一个包含当前月份所有订单的分区范围:
=> CREATE PROJECTION mtd_orders AS
SELECT * FROM store_orders
ON PARTITION RANGE BETWEEN
date_trunc('month', now())::date AND NULL;
如果您在 2021 年 7 月创建此分区,最小分区范围表达式 date_trunc('month', now())::date 最初解析为该月的第一天:2021-07-01。在下一个月开始时(大约在 2021-08-01 00:00 到 2021-08-01 01:00 之间),投影维护器会将最小范围表达式与系统时间进行比较,然后执行以下操作:
- 更新
PROJECTIONS表,将投影mtd_orders的PARTITION_RANGE_MIN设置为2021-08-01。 - 排队一个 MERGEOUT 请求,从该投影的分区范围中清除所有早于
2021-08-01的行。
重要提示 鉴于上述示例,您可能会考虑将投影的最大分区范围表达式设置为:
add_months(date_trunc('month', now()), 1) - 1此表达式将始终解析为当月的最后一天。每个月,最大分区范围都会比之前的值大一个月。如前所述,投影维护器会忽略分区范围的任何扩展,因此它将保持
mtd_orders的最小和最大分区范围值不变。为了避免此类问题,请始终将最大分区表达式设置为NULL。
6. 使用分区范围投影的查询 (Queries using partition range projections)¶
为了在查询中利用分区范围投影,您的查询必须允许数据库协调查询指定的范围与投影包含的范围。
考虑以下示例,其中已从表 store_orders 创建了分区范围投影 ytd_orders:
该查询按预期工作,因为 WHERE 的使用确保了从数据库的角度来看,查询范围内不可能有任何不符合条件的内容。更具体地说,WHERE 的使用为优化器提供了成功完成查询所需的范围跨度 (spans)。
然而,简单地使用 SELECT * FROM ytd_orders 实际上是要求数据库使用 ytd_orders 投影提供 store_orders 表中所有行的计数。这是无法完成的,因为数据库无法知道返回的所有订单都在分区范围投影指定的范围内。虽然看起来对分区范围投影使用 SELECT * 会返回投影的行计数,但这并不是分区范围投影的工作方式。数据库总是试图将分区范围投影查询与父表进行协调。
如果查询不够具体,数据库将返回以下错误:
ERROR 3586: Insufficient projections to answer query
DETAIL: No projections eligible to answer query
HINT: Projection ytd_orders not used in the plan because partition ranged projection does not cover the requested data range
扩展阅读¶
- Vertica 表分区策略选择指南 — 分区策略决策框架与 ROS 容器数控制
- 理解 Vertica 的分区 — 分区基础概念与存储裁剪原理
- Tuple Mover 最佳实践完全指南 — Mergeout 与分区交互机制