物化视图可以直接存储查询结果,在复杂查询场景下大幅提升查询效率。针对社区PostgreSQL普通物化视图刷新慢和刷新期间阻塞读的两大痛点,PolarDB PostgreSQL版提供了列存加速刷新、刷新不阻塞读、定时刷新等增强能力。
背景介绍
物化视图区别于普通视图,可以直接存储查询的结果。在复杂查询的场景下,使用物化视图保存查询结果可以大幅度提升查询效率。但物化视图中的数据不会随着基表数据的变化而变化,因此需要通过REFRESH MATERIALIZED VIEW命令重新刷新物化视图。
在社区PostgreSQL中,普通物化视图的刷新存在以下两大痛点:
刷新慢:
REFRESH MATERIALIZED VIEW会重新执行一遍物化视图的定义查询,再用结果替换旧数据。当基表数据量较大时,定义查询(通常是聚合、多表连接等分析型查询)的执行开销较大。刷新期间阻塞读:非
CONCURRENTLY方式的刷新在整个执行期间阻塞对该物化视图的所有查询,刷新耗时较长的大物化视图会长时间读不可用。REFRESH MATERIALIZED VIEW CONCURRENTLY虽然不阻塞读,但要求物化视图上存在不含WHERE子句的唯一索引,且采用逐行比对合并的方式,数据变化量大时刷新速度明显更慢。
PolarDB PostgreSQL版针对上述两大痛点,提供了以下增强能力:
列存加速刷新:刷新时的定义查询可以使用列存索引(IMCI)配合向量化引擎执行,分析型查询(聚合、多表连接等)的刷新速度显著提升。
刷新不阻塞读:开启后,刷新期间对物化视图的只读查询不再被阻塞。
定时刷新:借助
pg_cron扩展,可以将刷新配置为后台定时任务,无需外部调度系统。
适用范围
PostgreSQL 14:内核小版本需为2.0.14.20.43.0及以上版本。
列存加速刷新
原理
REFRESH MATERIALIZED VIEW本质上是重新执行一遍物化视图的定义查询,再用结果替换旧数据。物化视图的定义查询通常是聚合、多表连接等分析型查询,正是列存引擎擅长的场景。
开启列存查询后,刷新时的定义查询与普通查询一样参与列存路由判断:当基表上存在列存索引、且代价超过阈值时,查询将由列存索引(IMCI)执行器(基于列存副本与向量化引擎)执行,结果写回物化视图。REFRESH MATERIALIZED VIEW与REFRESH MATERIALIZED VIEW CONCURRENTLY均支持。
适用范围
新版本集群无需进行额外操作,早期版本集群需要先安装polar_csi插件(CREATE EXTENSION polar_csi;)。详细信息,请参见开启列存索引功能。
版本说明如下:
PostgreSQL 16(2.0.16.9.8.0及以上)或PostgreSQL 14(2.0.14.17.35.0及以上):无需额外操作。
PostgreSQL 16(2.0.16.8.3.0~2.0.16.9.8.0)或PostgreSQL 14(2.0.14.10.20.0~2.0.14.17.35.0):需安装插件。
使用示例
通过设置polar_csi.enable_query为on,即可让刷新的定义查询按列存路由规则执行。可通过会话级参数或Hint方式启用:
-- 会话级启用
SET polar_csi.enable_query = on;
SET polar_csi.cost_threshold = 0;
REFRESH MATERIALIZED VIEW mv_name;
-- Hint 方式启用
/*+Set(polar_csi.enable_query on) Set(polar_csi.cost_threshold 0) */REFRESH MATERIALIZED VIEW mv_name;查看刷新执行计划
开启polar_enable_explain_refresh_matview后,EXPLAIN支持直接解释REFRESH MATERIALIZED VIEW语句,展示刷新将使用的执行计划(也支持EXPLAIN ANALYZE实际执行):
SET polar_enable_explain_refresh_matview = on;
EXPLAIN REFRESH MATERIALIZED VIEW mv_name;
EXPLAIN ANALYZE REFRESH MATERIALIZED VIEW mv_name;刷新不阻塞读
背景
社区PostgreSQL中,REFRESH MATERIALIZED VIEW(非CONCURRENTLY)在整个刷新期间阻塞对该物化视图的所有查询:刷新执行多久,读就被阻塞多久。对刷新耗时较长的大物化视图,这意味着长时间的读不可用。
REFRESH MATERIALIZED VIEW CONCURRENTLY虽然不阻塞读,但要求物化视图上存在不含WHERE子句的唯一索引,且采用逐行比对合并的方式,数据变化量大时刷新明显更慢。
使用方式
通过开启polar_enable_reduce_refresh_matview_lockmode,即可让非CONCURRENTLY的刷新期间不再阻塞对物化视图的只读查询:
SET polar_enable_reduce_refresh_matview_lockmode = on;
REFRESH MATERIALIZED VIEW mv_name;开启后,非CONCURRENTLY的刷新执行期间(包括定义查询执行、新数据写入、索引重建的全过程),对该物化视图的只读查询不再被阻塞,读取到的是刷新前的旧数据;刷新完成的收尾阶段仍有短暂的互斥(保证只读节点回放的正确性),此时若存在尚未提交的读事务,刷新会等待其结束后再提交。
物化视图索引的并行重建(parallel index build)同样不会阻塞读,您无需额外配置。
三种刷新方式对比
刷新方式 | 刷新期间读 | 唯一索引要求 | 刷新代价 |
| 全程阻塞 | 无 | 全量重建,快 |
| 不阻塞(读旧数据) | 无 | 全量重建,快 |
| 不阻塞(读旧数据) | 需要无 | 逐行比对合并,变化量大时慢 |
注意事项
同一物化视图上的多个刷新(含
CONCURRENTLY)之间仍互斥,串行执行。长时间未提交的读事务会推迟刷新的收尾提交,建议避免在长事务中持续访问正在刷新的物化视图。
该参数对
REFRESH MATERIALIZED VIEW CONCURRENTLY无影响(其本身即不阻塞读)。
定时刷新
普通物化视图不会随基表更新自动刷新,需要显式执行REFRESH MATERIALIZED VIEW。借助pg_cron扩展,可以将刷新配置为后台定时任务,无需外部调度系统。
pg_cron默认在postgres库中调度任务。连接到postgres库后创建扩展,即可开始配置定时任务:
-- 在 postgres 库中执行
CREATE EXTENSION IF NOT EXISTS pg_cron;
-- 每 10 分钟刷新一次物化视图
SELECT cron.schedule(
'refresh-mv-name',
'*/10 * * * *',
$$REFRESH MATERIALIZED VIEW your_schema.your_mv;$$
);参数参考
参数 | 默认值 | 级别 | 说明 |
|
| 会话级 | 允许 |
|
| 会话级 | 开启后,非 |
|
| 会话级 | 允许查询(含物化视图刷新的定义查询)路由到列存执行。 |
|
| 会话级 | 路由到列存的代价阈值,设为 |