likes
comments
collection
share

PostgreSQL技术问答35 - Materialized View实体化视图本文探讨了Postgres的一个重要的

作者站长头像
站长
· 阅读数 23

本文是《PostgreSQL技术问答》系列文章中的一篇。关于这个系列的由来,可以参阅开篇文章:

文章的编号只是一个标识,在系列中没有明确的逻辑顺序和意义。读者进行阅读时,不用太关注这个方面。

本文主要讨论的内容是在Postgres中,一个特殊形式的视图: Materialized View(MV),实体化视图,有的系统中也被称为物化视图。

什么是实体化视图(Materialized View, MV)

实体化视图,顾名思义,就是将视图实体化了。原本视图就是一个逻辑的概念,它的呈现方式虽然像是一个正常数据表,但它的实质和内容,其实是一个查询。当在任何形式中引用视图的时候,数据库系统会实时的执行这个查询,最终把结果呈现成为一个表的形式,来支持这个引用操作。那么我们就会很容易想到,如果这个查询比较复杂,那么每次引用视图的时候,构造内容的过程可能就会比较慢。为了改善这个问题,人们就会想到,是否可以将查询的内容,像真正的数据表一样,存储在真正的存储设备(比如硬盘)中,这样在引用的过程中,就可以向引用真正的表一样,快速的获取数据,从而改善查询的性能。这个将查询结果缓存下来的过程,就是“实体化(Materialized)”。

PostgreSQL技术问答35 - Materialized View实体化视图本文探讨了Postgres的一个重要的

所以,实体化视图的实质,就是将视图和查询的结果缓存下来,提升后续使用和查询的性能。就是一个利用缓存来优化性能的机制。所以,实体化视图并不是什么“黑科技”,就是一种工程化的优化方式,需要根据场合合理选择使用,并不是万能的技术手段。

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
评论
请登录