PostgreSQL技术问答35 - Materialized View实体化视图本文探讨了Postgres的一个重要的
本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:
文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。
本文主要讨论的内容是在Postgres中,一个特殊形式的视图: Materialized View(MV),实体化视图,有的系统中也被称为物化视图。
什么是实体化视图(Materialized View, MV)
实体化视图,顾名思义,就是将视图实体化了。原本视图就是一个逻辑的概念,它的呈现方式虽然像是一个正常数据表,但它的实质和内容,其实是一个查询。当在任何形式中引用视图的时候,数据库系统会实时的执行这个查询,最终把结果呈现成为一个表的形式,来支持这个引用操作。那么我们就会很容易想到,如果这个查询比较复杂,那么每次引用视图的时候,构造内容的过程可能就会比较慢。为了改善这个问题,人们就会想到,是否可以将查询的内容,像真正的数据表一样,存储在真正的存储设备(比如硬盘)中,这样在引用的过程中,就可以向引用真正的表一样,快速的获取数据,从而改善查询的性能。这个将查询结果缓存下来的过程,就是“实体化(Materialized)”。
所以,实体化视图的实质,就是将视图和查询的结果缓存下来,提升后续使用和查询的性能。就是一个利用缓存来优化性能的机制。所以,实体化视图并不是什么“黑科技”,就是一种工程化的优化方式,需要根据场合合理选择使用,并不是万能的技术手段。
Postgres从版本12开始支持实体化视图的特性。笔者记得好像Oracle很早(起码10G)就有这个特性了。 这说明这个特性,也是有广泛而稳定的需求的。
如何定义和使用MV
定义MV的方式,基本上和使用视图的方式是相同的,差异就是语法稍微不同。它的标准形式如下:
CREATE MATERIALIZED VIEW [ IF NOT EXISTS ] table_name
[ (column_name [, ...] ) ]
[ USING method ]
[ WITH ( storage_parameter [= value] [, ... ] ) ]
[ TABLESPACE tablespace_name ]
AS query
[ WITH [ NO ] DATA ]
这里可以看到有一些要点:
- Create Materialized View 是创建MV的声明形式和关键字
- table_name,可以看到,创建MV更像建立一个真正的表
- AS 是关键字,引领定义查询语句
- Using,指定访问MV内容的方式
- With params, 在创建时,可以设置一些选项和参数,和table类似
- TableSpace,由于是实体化的,可以设置和使用特定的表空间
- With [NO] Data,可以在创建时,指定装载或者不装载数据(执行一次查询并插入MV表)
- 默认会With Data,即会进行数据查询和填充
- MV是数据库对象,它会出现在架构对象列表中,类型就是 Materialized View
由于和简单视图相比,实体化视图在实现中,需要对查询数据进行存储,就是实体化。由于这个特性,显然在MV的应用过程和生命周期管理中,就增加了一个很重要的环节,就是MV的刷新,这也是我们理解实体化视图技术的核心。我们在后面重点详细展开讨论。
如何进行MV的刷新
刷新MV的命令非常简单,就是一个命令语句,它的标准形式如下:
REFRESH MATERIALIZED VIEW [ CONCURRENTLY ] name [ WITH [ NO ] DATA ]
除了Refresh Maiterialzied View 这个指令关键字之外,这里面有两个选项:
- Concurrently,设置刷新操作和其他会话并发执行,提高可访问性能
- With No Data,这个选项会清除MV中的数据,并且MV将不可扫描,相当于禁用
但是,这个问题的关键,并不是如何执行这个刷新操作,而是何时进行。经过查询相关的技术文档,笔者发现Postgres好像对MV的实现,并不是特别完善和强大,主要就是体现在对刷新操作的优化上。以Oracle为例,它的MV刷新,可以选择设置成为自动化,可以在事务提交时,自动执行实体化视图刷新;此外它也可以支持增加刷新,从而大大提高刷新的效率。这些特性,好像当前的Postgres版本中都是缺失的。按照文档的说法,它的每次刷新,基本上都会将数据重建一遍,虽然通过副本实例的方式,提高了刷新操作时的可访问性,但笔者确实有点担心对于比较大的表,MV数据刷新的性能和对系统的影响。
在这种情况下,刷新机制的设计就更加重要了。可能需要一些外部的辅助程序来帮助进行变更的感知和刷新执行,如果简单的使用触发器来控制刷新,对于频繁更新的数据表,将会产生大量的数据更新操作,可能会对系统和服务都造成比较大的性能影响。
所以,根据不同的场景和情况,我们可以考虑下列更新的操作和实现策略:
- 实时刷新
需要在数据表上部署一个触发器,数据更新时,调用更新命令来执行MV的刷新。这个操作方式,基本上实现了数据的实时刷新,在查询时也可以保证数据是最新的。但这种方式要特别注意更新频率和数据规模对于系统性能的影响。
- 定时刷新
如果业务需求可以接受,而且数据的更新相对比较平均和稳定,可以考虑在内部或者外部部署一个定时器,定时触发实体化视图的更新。这个操作,对于系统性能的影响相对较小和稳定。特别是如果不要求实时性,都可以考虑在业务低谷的时候,如凌晨来进行这种操作。
- 按需刷新
根据某种业务访问的策略,决定何时来进行刷新,比如,在查询前,如果确定需要最新的数据,可以进行一次MV的刷新。一个比较好的操作实践,就是将查询和更新异步化。就是当用户访问时,可以基于MV快速提供当前的内容,然后在执行一遍刷新来更新MV中的数据,保证数据的实时性。
实体化视图可以使用索引吗
当然可以。对索引的支持和使用,其实是实体化视图的一个在性能优化方面的重要特性。
在实体化视图对象上,创建和增加索引的方式,和普通的实体数据表并没有差异。只需要理解,这个索引是针对这个实体化视图的,和其引用的数据库表没有直接的逻辑关系,不会相互影响。
如何修改和删除实体化视图
Postgres提供了相关的命令,来执行修改和删除实体化视图的操作。修改实体化视图,比我们一般的想象要复杂一些,下面是它的标准形式:
ALTER MATERIALIZED VIEW [ IF EXISTS ] name action [, ... ]
ALTER MATERIALIZED VIEW name [ NO ] DEPENDS ON EXTENSION extension_name
ALTER MATERIALIZED VIEW [ IF EXISTS ] name RENAME [ COLUMN ] column_name TO new_column_name
ALTER MATERIALIZED VIEW [ IF EXISTS ] name RENAME TO new_name
ALTER MATERIALIZED VIEW [ IF EXISTS ] name SET SCHEMA new_schema
ALTER MATERIALIZED VIEW ALL IN TABLESPACE name [ OWNED BY role_name [, ... ] ]
SET TABLESPACE new_tablespace [ NOWAIT ]
where action is one of:
ALTER [ COLUMN ] column_name SET STATISTICS integer
ALTER [ COLUMN ] column_name SET ( attribute_option = value [, ... ] )
ALTER [ COLUMN ] column_name RESET ( attribute_option [, ... ] )
ALTER [ COLUMN ] column_name SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN | DEFAULT }
ALTER [ COLUMN ] column_name SET COMPRESSION compression_method
CLUSTER ON index_name
SET WITHOUT CLUSTER
SET ACCESS METHOD new_access_method
SET TABLESPACE new_tablespace
SET ( storage_parameter [= value] [, ... ] )
RESET ( storage_parameter [, ... ] )
OWNER TO { new_owner | CURRENT_ROLE | CURRENT_USER | SESSION_USER }
可以修改的东西很多,包括MV重命名,给字段重命名,设置架构,设置表空间,修改字段选项等等。
删除实体化视图,使用Drop Materialize View 命令。
这些操作包括MV生命周期的管理,和视图其实是类似的,这里就不再重复了。
实体化视图和复制表有什么区别
确实,在Postgres中,可以很方便的从一个查询创建一个实体的表。
-- 实体化视图
CREATE MATERIALIZED VIEW mymatview
AS SELECT * FROM mytab;
-- 新数据表
CREATE TABLE mymatview
AS SELECT * FROM mytab;
但这里面还是有一定区别的。MV显然可以更方便的更新数据,而复制表可能需要先删除然后创建,代价更高一点。还有系统对MV的更新,有一定的优化机制(并行刷新),可能在数据更新方面有更好的性能。
使用MV需要注意什么问题
和所有的数据缓存系统一样,MV本身在实现机制上,就带有缓存数据的一些问题:
- 实时性
MV只是将查询的结果缓存下来使用,它并不是完全正常实时的查询。在大多数情况下,它可以很接近实时查询的内容,但确实并不是真正的实时查询。它的准确性和实时性,取决于缓存更新的机制。
- 场景限制
作为一个缓存技术,要发挥其应有的效能和优势,就需要根据使用的场景合理的选择和应用。笔者认为,在Postgres中,MV适合于那种更新不是特别频繁,但对查询性能要求比较高的场合。还有一种常见的场景就是统计信息,例如可以使用MV保存统计分析的结果,一般情况下,可以立即响应用户的查询请求(如果用户对实时数据要求不高),用户也可以选择强行在查询前进行刷新,获得最新数据,这样可以很好的平衡的平衡数据的准确性、查询性能和对系统运行的影响。
- 数据更新
数据更新的时机比较重要。可以选择定时更新、有变更时更新(使用触发器或者其他数据检查机制)、查询前更新等方式。取决于对数据实时性的要求,和对系统运行影响的评估。这里有一个基本的原则就是按需更新,避免无效重复更新,尽量减少对系统正常运行的影响。
- 数据完整性冲突
在使用实体化视图的过程中,特别是刷新视图时,PostgreSQL会删除现有的物化视图数据并重新计算。如果物化视图的基础表或其他依赖表在此期间发生了变化,则刷新后的物化视图可能会包含不一致或不完整的数据。因此,在刷新物化视图之前,需要确保基础表和依赖表的数据完整性。
- 性能影响
刷新物化视图可能会导致性能问题,因为PostgreSQL需要重新计算物化视图的所有行。如果物化视图包含大量数据或复杂的计算,则刷新可能需要很长时间。因此,请在非高峰期或预留足够的时间来刷新物化视图。
如果查询定义本身比较复杂的情况下,就需要更加合理的规划数据的更新,避免由于频繁更新增加系统的负载,影响系统正常服务的性能。
- 空间占用
MV本质上会生成和存储数据的副本,并且MV可以和正常的数据表一样,创建独立的索引,这些会占用和消耗一定的磁盘空间,这点需要数据库管理人员有清晰的理解和认知。
- 访问冲突
在进行实体化视图的刷新操作时,PostgreSQL会锁定实体化以防止并发访问。如果其他会话尝试访问正在刷新的物化视图,则可能会导致错误或性能问题。因此,在刷新物化视图时,请确保没有其他会话在访问物化视图,或者使用适当的锁定机制来控制并发访问。
- 错误处理
刷新物化视图时可能会出现错误,例如数据完整性错误、内存不足错误等。需要确保应用程序可以正确处理这些错误,并采取适当的措施来恢复数据或通知用户。
了解了这些限制和问题,可以让我们更好的将实体化视图技术,合理的运用到业务系统当中。
小结
本文探讨了Postgres的一个重要的特性: Materialized View,即实体化视图,它名为视图,但实际上是一种缓存和性能优化方案。文中讨论了其基本定义,使用的过程和需要注意的问题等方面的内容。
转载自:https://juejin.cn/post/7406147685294440502