增量分区物化视图是PolarDB PostgreSQL版提供的物化视图类型,把分区物化视图的分区管理能力与增量物化视图(DIMV)的增量刷新引擎结合起来,通过polar_matview.create_partition_mv的use_dimv => TRUE参数创建。适用于按时间滚动分区、历史分区写入后不再变化的大事实表,在这类基表上以较低的维护成本提供秒级新鲜度的聚合结果。
概述
增量分区物化视图把分区物化视图的分区管理能力与增量物化视图(DIMV)的增量刷新引擎结合起来,通过polar_matview.create_partition_mv(..., use_dimv => TRUE)创建:每个源分区对应一个真正的DIMV子物化视图,源分区结构的增删由分区物化视图负责同步,分区内部的数据变更由DIMV引擎增量维护。
适用场景
最典型的场景是在按时间滚动分区的大事实表(例如订单、流水、日志、埋点)上维护聚合报表。这类场景通常同时具备以下特征,而它们正好逐条对应增量分区物化视图提供的能力:
基表很大且持续增长,全量重算一次的代价高昂、资源消耗大,需要增量刷新。
每次改动量相对表规模很小,增量刷新才划算。
写入集中在最近的少数几个分区,历史分区写完就基本不再变化,可以被冻结、彻底退出维护。
分区在不断滚动,新分区需要自动纳入维护,下线的分区需要自动从结果中移除。
对数据新鲜度有要求,希望秒级而不是天级看到最新聚合结果。
反过来,如果基表没有分区,或者每次刷新本来就要重算大部分数据,或者每天全量刷新一次就足够,那么使用普通物化视图或分区物化视图更简单。
与另外两种物化视图的差异
分区物化视图和增量物化视图各自解决了一半问题:
分区物化视图能把刷新范围收敛到发生变化的分区,但单个分区内部仍是全量重算。分区越大,一次改动的代价越高。
增量物化视图能把刷新代价压到与改动量成正比,但它不感知分区。一方面,随着基表持续增长,历史数据虽然不再变化,仍然要留在同一个物化视图里参与维护;另一方面,分区的增删本身不产生物化视图日志(Materialized View Log,简称mlog)记录,因此
DROP或DETACH掉一个源分区后,DIMV不会把该分区的贡献从结果中剔除,需要手动全量刷新,而分区物化视图能通过结构对齐自动处理。
组合之后,一次基表改动的刷新代价既被分区裁剪限制在相关分区内,又在分区内部只处理增量行;分区的增删由分区物化视图提供的结构对齐能力负责;长期没有变化的历史分区还可以被冻结,彻底退出自动刷新的调度范围,使维护开销不随历史数据累积而增长。
代价是多了DIMV的那份约束:基表需要主键,查询定义要满足DIMV对SQL的限制,写入时要承担mlog的记录开销。各类物化视图的完整对比与选型建议,请参见物化视图概览。
适用范围
PostgreSQL 14:内核小版本需为2.0.14.22.48.0及以上版本。
安装扩展
增量分区物化视图依赖polar_ivm扩展。使用前请在需要使用该功能的数据库中执行以下SQL安装扩展:
CREATE EXTENSION IF NOT EXISTS polar_ivm;使用要求
在分区物化视图的使用条件(RANGE分区、单列分区键、分区键必须在GROUP BY中、分区表不能出现在子查询或JOIN可空侧等)之上,增量分区物化视图还有以下要求:
查询定义必须同时满足增量物化视图对SQL的要求:子物化视图是真正的DIMV,因此DIMV支持的聚合函数、连接形式等限制在这里同样适用,详情请参见增量物化视图。
基表必须能唯一标识行:这同样是DIMV带来的限制,mlog要靠这个标识把记录下来的变更行对应回物化视图中的行,详情请参见增量物化视图。
PostgreSQL模式下要求基表显式定义
PRIMARY KEY。PostgreSQL又要求分区表的主键必须包含分区键列,因此主键通常是“分区键+业务主键”的复合主键。
不支持DEFAULT分区:DEFAULT分区的边界是开放的,无法映射到一个固定范围的子物化视图,创建时会直接报错:
ERROR: partition DIMV does not support a DEFAULT partition on "..."
创建
使用polar_matview.create_partition_mv创建增量分区物化视图,把use_dimv置为TRUE即可,其余参数的含义与分区物化视图一致:
CALL polar_matview.create_partition_mv(
mv_schema NAME, -- 物化视图的 schema
mv_name NAME, -- 物化视图名称
ref_partition_table OID, -- 源分区表的 OID
view_def TEXT, -- SELECT 查询定义
use_imci BOOLEAN DEFAULT FALSE, -- 是否使用列存加速
skip_check_partitionable BOOLEAN DEFAULT FALSE, -- 跳过可分区性检查
create_no_data BOOLEAN DEFAULT FALSE, -- 创建时不填充数据
use_dimv BOOLEAN DEFAULT FALSE -- 用 DIMV 承载增量刷新
);参数说明:
参数 | 类型 | 默认值 | 说明 |
| NAME | 必填 | 物化视图的Schema。 |
| NAME | 必填 | 物化视图名称。 |
| OID | 必填 | 源分区表的OID。 |
| TEXT | 必填 |
|
| BOOLEAN | FALSE | 是否使用列存加速。 |
| BOOLEAN | FALSE | 跳过可分区性检查。 |
| BOOLEAN | FALSE | 创建时不填充数据。 |
| BOOLEAN | FALSE | 用DIMV承载增量刷新。置为 |
示例:在按季度RANGE分区的demo.sales表上创建增量分区物化视图。
CREATE SCHEMA IF NOT EXISTS demo;
-- 基表:RANGE 按季度分区,主键包含分区键 sale_date
CREATE TABLE demo.sales (
id SERIAL,
sale_date DATE,
amount INT,
PRIMARY KEY (id, sale_date)
) PARTITION BY RANGE (sale_date);
CREATE TABLE demo.sales_q1 PARTITION OF demo.sales
FOR VALUES FROM ('2023-01-01') TO ('2023-04-01');
CREATE TABLE demo.sales_q2 PARTITION OF demo.sales
FOR VALUES FROM ('2023-04-01') TO ('2023-07-01');
INSERT INTO demo.sales(sale_date, amount) VALUES
('2023-02-15', 100), ('2023-03-15', 150), ('2023-05-15', 200);
-- 创建增量分区物化视图
CALL polar_matview.create_partition_mv(
'demo', 'mv_sales', 'demo.sales'::regclass,
$$SELECT sale_date, SUM(amount) AS s FROM demo.sales GROUP BY sale_date$$,
FALSE, FALSE, FALSE, TRUE /* use_dimv */
);
SELECT * FROM demo.mv_sales ORDER BY 1;创建时系统会完成以下工作:
为每个源分区对应创建一个
REFRESH FAST ON DEMAND ... WITH NO DATA的子DIMV,随后执行一次全量刷新填充数据、建立增量基线。所有子DIMV共享基表上的同一个mlog,基表的写入开销不随分区数增加。
自动刷新间隔初始为
-1 second,即默认关闭,需要显式调用set_refresh_interval开启。
刷新
自动刷新(推荐)
在父物化视图上调用polar_matview.set_refresh_interval,间隔会记录在分区物化视图的元数据中,并下推到当前所有子DIMV;此后新增分区产生的子DIMV也会继承这个间隔。
-- 将自动刷新间隔设置为 10 秒
SELECT polar_matview.set_refresh_interval(
'demo.mv_sales'::regclass::oid,
'10 seconds'::interval);之后由后台worker自动完成增量刷新,worker的部署方式与普通DIMV完全一致,请参见增量物化视图中的“配置自动刷新与清理”章节。worker无需为分区场景做任何特殊配置:子DIMV就是普通DIMV,同样出现在调度队列里。
结构对齐
源分区表增删分区后,调用polar_matview.refresh_partition_mv自动同步结构:
CALL polar_matview.refresh_partition_mv('demo', 'mv_sales');新增源分区:创建对应的子DIMV(
WITH NO DATA+自动建mlog+一次全量刷新填充),并继承父物化视图上的刷新间隔。删除源分区:对应的子物化视图被卸载并删除。基表上的mlog不会被自动删除,因为它可能仍被其他物化视图共享。
冻结历史分区
时间序列场景中,老分区在窗口滑过之后就不再变化,但只要它们的子DIMV还在DIMV的接管之下,调度器每一轮都要把它们取出来、开事务、查一遍增量、再更新元数据,即使每次都发现无事可做。分区数越多,这部分与数据量无关的固定开销占比越高。冻结机制把这些分区从调度队列里摘出去,让维护成本只落在真正活跃的分区上,因此推荐开启。
开启冻结功能
在父物化视图上设置冻结时长即可开启,该功能默认关闭:
-- 源分区超过 7 天没有数据变化,其子 DIMV 被冻结
SELECT polar_matview.set_freeze_interval('demo.mv_sales'::regclass, INTERVAL '7 days');
-- 传 NULL 关闭冻结
SELECT polar_matview.set_freeze_interval('demo.mv_sales'::regclass, NULL);使用说明
判定与执行:均发生在
refresh_partition_mv里,系统为每个子物化视图记录最后一次源数据变化的时间,本轮刷新中没有观察到源分区变化、且距上次变化已超过freeze_interval的子DIMV会被冻结。冻结效果:清除该子DIMV的可增量更新标记,使它不再被自动刷新的调度队列选中。物化视图本身仍然可查,数据保持冻结时刻的状态。
自动恢复:如果之后某个已冻结的历史分区又发生了数据变更,在下一次执行
refresh_partition_mv时会检测到版本变化,对该子物化视图执行一次全量刷新使其恢复,此后它重新回到增量维护状态。数据没有发生变化的兄弟分区不受影响,继续保持冻结。强制冻结:如需绕过时间判定直接冻结某个子物化视图,可以调用:
SELECT polar_matview.freeze_dimv('demo.mv_sales_q1'::regclass);
最佳实践
用pg_cron定期驱动结构对齐
借助pg_cron扩展把refresh_partition_mv配成一个定期任务,让它同时承担三件事:同步源表新增或删除的分区、把新分区纳入增量维护、探测冷分区上的数据变化。
pg_cron的任务元数据集中在一个库中(由cron.database_name指定,默认为postgres),cron.*函数也只存在于该库。因此这一步要连到该库执行,用schedule_in_database的最后一个参数指定物化视图所在的业务库;执行者需要是普通角色,可以直接复用配置DIMV worker时创建的dimv_worker角色。
-- 连接到 pg_cron 的元数据库(默认 postgres),以 dimv_worker 身份执行
SELECT cron.schedule_in_database(
'align-mv-daily-sales',
'59 seconds',
$$CALL polar_matview.refresh_partition_mv('demo', 'mv_daily_sales')$$,
'<业务库>'
);这个间隔决定两件事的延迟:新滚出来的分区多久被自动纳入维护,以及冷分区在重新发生数据变化后多久转回热分区。按业务能接受的延迟配置即可,不必追求过密,因为每一轮都要遍历所有子物化视图做版本比对,间隔过小会让这部分固定开销占比上升。
pg_cron的秒级间隔语法只接受1~59秒,上面的'59 seconds'即“约每分钟一轮”;需要更长的周期改用标准cron表达式,例如'*/5 * * * *'表示每5分钟一轮。
这个定期任务是冻结分区能自动转热的前提。冻结的子物化视图不再被DIMV的调度器刷新,但refresh_partition_mv每一轮仍会检查它对应的源分区版本;一旦历史分区上又出现数据变更,这一轮refresh_partition_mv就会检测到版本变化、对该子物化视图执行一次全量刷新把它恢复,此后重新交由DIMV增量维护,整个过程不需要人工介入。
初始即全部冻结,按需转热
对于分区很多、但活跃分区只有少数几个的场景(典型的时间序列表),可以在创建完成后把所有子物化视图一次性冻结:
DO $$
DECLARE c regclass;
BEGIN
FOR c IN SELECT inhrelid::regclass FROM pg_inherits
WHERE inhparent = 'demo.mv_daily_sales'::regclass LOOP
PERFORM polar_matview.freeze_dimv(c);
END LOOP;
END $$;这样一开始没有任何子物化视图占用自动刷新的调度资源。之后哪个分区的数据发生了变化,上面那个定期的refresh_partition_mv就会把它恢复成热分区、交给DIMV接管;数据始终没有变化的历史分区则一直保持冻结。
这种方式不依赖set_freeze_interval:set_freeze_interval解决的是“热分区变冷后自动退出”,初始冻结解决的是“一开始就只让真正活跃的分区占用维护资源”。两者建议叠加使用:只做初始冻结的话,被恢复成热分区的子物化视图此后再也不会转冷,随着写入窗口向前滑动,热分区只增不减;配上set_freeze_interval,它们在不再变化足够久之后才会重新冻结。
列存索引加速
use_imci参数对增量分区物化视图同样有效,创建和刷新时会走列存执行路径:
CALL polar_matview.create_partition_mv(
'demo', 'mv_sales_csi', 'demo.sales'::regclass,
$$SELECT sale_date, SUM(amount) AS s FROM demo.sales GROUP BY sale_date$$,
TRUE /* use_imci */, FALSE, FALSE, TRUE /* use_dimv */
);
CALL polar_matview.refresh_partition_mv('demo', 'mv_sales_csi', TRUE /* use_imci */);use_imci会自动为建视图和全量刷新语句注入Set(polar_csi.enable_query on)和Set(polar_csi.cost_threshold 0),无需再改会话配置,也不受默认代价阈值影响。唯一的前置条件是基表上存在列存索引(IMCI),创建方法请参见普通物化视图中的列存加速刷新章节。
删除
使用polar_matview.drop_partition_mv删除增量分区物化视图:
CALL polar_matview.drop_partition_mv('demo', 'mv_sales');该操作会删除父物化视图、所有子DIMV及相关元数据。与删除单个分区一样,基表上的mlog不会被自动删除。
完整示例
下面是一个端到端示例,覆盖从安装扩展、创建增量分区物化视图、开启自动刷新、冻结历史分区,到定期结构对齐与清理的完整流程。
-- ========== 1. 准备扩展与基表 ==========
CREATE EXTENSION IF NOT EXISTS polar_ivm;
CREATE SCHEMA IF NOT EXISTS demo;
CREATE TABLE demo.orders (
order_id SERIAL,
order_date DATE,
user_id INT,
amount NUMERIC,
PRIMARY KEY (order_id, order_date)
) PARTITION BY RANGE (order_date);
CREATE TABLE demo.orders_202401 PARTITION OF demo.orders
FOR VALUES FROM ('2024-01-01') TO ('2024-02-01');
CREATE TABLE demo.orders_202402 PARTITION OF demo.orders
FOR VALUES FROM ('2024-02-01') TO ('2024-03-01');
INSERT INTO demo.orders(order_date, user_id, amount) VALUES
('2024-01-05', 1, 100), ('2024-01-20', 2, 200),
('2024-02-10', 1, 300);
-- ========== 2. 创建增量分区物化视图 ==========
CALL polar_matview.create_partition_mv(
'demo', 'mv_daily_sales', 'demo.orders'::regclass,
$$SELECT order_date,
SUM(amount) AS total_amount,
COUNT(*) AS order_count
FROM demo.orders
GROUP BY order_date$$,
FALSE, FALSE, FALSE, TRUE /* use_dimv */
);
SELECT * FROM demo.mv_daily_sales ORDER BY order_date;
-- ========== 3. 开启自动刷新(间隔下推到所有子 DIMV) ==========
SELECT polar_matview.set_refresh_interval(
'demo.mv_daily_sales'::regclass::oid, '10 seconds'::interval);
-- 增量刷新由后台 worker 执行,set_refresh_interval 本身不会触发刷新。
-- 必须先连到 pg_cron 的元数据库(默认 postgres)调度 worker,
-- 否则物化视图的数据会一直停留在创建时的快照:
-- SELECT cron.schedule_in_database(
-- 'dimv-auto-refresh-worker-1', '1 second',
-- $$CALL polar_matview.auto_refresh_worker_main()$$, '<业务库>');
-- worker 的部署方式与普通 DIMV 完全一致,详见「增量物化视图」的“配置自动刷新与清理”章节。
-- ========== 4. 数据变更由 DIMV 增量应用 ==========
INSERT INTO demo.orders(order_date, user_id, amount) VALUES ('2024-01-05', 3, 50);
UPDATE demo.orders SET amount = 400 WHERE order_date = '2024-02-10';
-- worker 调度后等待一个刷新周期(本例为 10 秒)再查询,即可看到最新结果
SELECT * FROM demo.mv_daily_sales ORDER BY order_date;
-- ========== 5. 冻结不再变化的历史分区 ==========
SELECT polar_matview.set_freeze_interval(
'demo.mv_daily_sales'::regclass, INTERVAL '30 days');结构对齐需要配成pg_cron定期任务。这一步在pg_cron的元数据库(默认为postgres)中、以普通角色执行,schedule_in_database的最后一个参数是上面这些对象所在的业务库:
-- ========== 6. 定期结构对齐(连到 postgres 库,以 dimv_worker 身份执行) ==========
SELECT cron.schedule_in_database(
'align-mv-daily-sales',
'59 seconds',
$$CALL polar_matview.refresh_partition_mv('demo', 'mv_daily_sales')$$,
'<业务库>'
);回到业务库,滚动一个新分区,等待一轮定期任务即可看到它被自动纳入维护:
-- ========== 7. 滚动新分区:由步骤 6 的定期任务自动纳入增量维护 ==========
CREATE TABLE demo.orders_202403 PARTITION OF demo.orders
FOR VALUES FROM ('2024-03-01') TO ('2024-04-01');
INSERT INTO demo.orders(order_date, user_id, amount) VALUES ('2024-03-15', 2, 500);
-- 等待一轮(约 1 分钟)后,新分区的子物化视图已建好、数据已填充,
-- 并继承了 10 秒的刷新间隔;也可以手动立即执行一次:
CALL polar_matview.refresh_partition_mv('demo', 'mv_daily_sales');
SELECT * FROM demo.mv_daily_sales ORDER BY order_date;
-- ========== 8. 清理 ==========
-- 在 postgres 库执行,取消定期任务
SELECT cron.unschedule('align-mv-daily-sales');
CALL polar_matview.drop_partition_mv('demo', 'mv_daily_sales');工作原理
理解这个特性的关键是记住分区物化视图和DIMV引擎各管一块,两者不重叠:
变化类型 | 由谁处理 | 具体行为 |
源分区表新增/删除分区 | 分区物化视图 |
|
分区内的数据变更 | DIMV引擎 | 通过mlog计算增量并合并进对应的子物化视图。 |
历史分区长期无变化 | 分区物化视图 | 按 |
冻结分区重新发生变化 | 分区物化视图 |
|
这解释了两个容易困惑的现象:
对一个增量分区物化视图执行
refresh_partition_mv,如果只有数据变化而结构没变,它会跳过该物化视图,数据看起来没有推进,因为数据归DIMV引擎自己维护,分区物化视图这一侧不去碰。反过来,被冻结的子物化视图不再由DIMV引擎维护,却仍然能在源分区的数据重新发生变化时自动恢复,因为版本检测和恢复动作都发生在
refresh_partition_mv这一侧。
注意事项
改名是安全的:元数据以OID和
pg_inherits为准,不依赖名称。执行ALTER MATERIALIZED VIEW ... RENAME之后,用新名字继续管理即可,增量维护不受影响。mlog不会被自动删除:删除分区或删除整个物化视图时,基表上的mlog都会保留,因为它可能被其他物化视图共享。确认不再需要时,请使用手动管理的接口清理,详情请参见增量物化视图。
刷新间隔设在父物化视图上:直接对某个子DIMV调用
set_refresh_interval只影响这一个子视图,父物化视图上记录的间隔不会随之改变;下次在父物化视图上设置间隔时,该子视图会被一并覆盖。