暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

『 TechTalk 』GaussDB(DWS)视图解耦与重建功能介绍

GaussDB DWS 2024-05-08
337

  • 适用版本:8.1.0(及以上)

GaussDB(DWS)数仓产品内部使用对象标识符(oid)来保存对象之间的引用关系,这使得视图在定义时就绑定了其依赖的基表的oid。

如果要删除字段或整个表,就需要连同其关联的视图一起使用cascade关键字删除,表修改完成后再重建各级视图,这就给用户的使用增加了很大的工作量。

为了解决这一问题,GaussDB(DWS) 在8.1.0版本实现了视图的解耦,8.1.1版本在此基础上又实现了自动刷新,8.2.1版本实现了本地自动刷新,避免了自动刷新时持锁周期过长、持锁粒度过大的问题。

本篇将带你了解GaussDB(DWS)视图解耦功能实现原理以及适用场景。


GaussDB(DWS)数仓产品内部使用对象标识符(oid)来保存对象之间的引用关系,这使得视图在定义的时候就绑定了其依赖的数据库对象的oid,而不管其名称怎么改变,都不会改变这层依赖关系。

如果要对基表进行一些字段修改,会因为与视图字段存在强绑定而报错,如果要删除某个表字段或整个表,就需要连同其关联的视图一起使用cascade关键字删除,表字段删除完成或表重建后再顺序重建各级视图,这就给用户的使用增加了很大的工作量,相对于某些市场主流数仓产品存在明显的易用性差异。

为了解决这一问题,GaussDB(DWS) 实现了视图的解耦,使得存在视图依赖的基表或其他数据库对象(视图、同义词、函数、表字段)可以单独删除,而其上关联的依赖视图依然存在,而在基表重建后,可以通过ALTER VIEW [ONLY] view_name REBUILD命令重建依赖关系。不同版本之间对于视图解耦功能的优化如下:

版本实现或优化内容备注
8.1.0视图依赖的基表或其他数据库对象(视图、同义词、函数、表字段)可以单独删除,删除后视图无效,需要手动重建仅支持手动视图重建,不方便客户使用
8.1.1实现对于无效视图的自动重建,再DML语句中使用到视图后便可触发重建DML语句中使用无效视图将持视图八级锁,待事务结束后释放,持锁周期过长、持锁粒度过大
8.2.1实现对于无效视图的本地自动重建,不更新系统表,视图一直表象为无效状态,但实际可用DML语句中使用无效视图后将对视图定义做展开操作,作为子查询形式,解决持锁问题
8.3.0支持ALTER VIEW [ONLY] view_name SET INVALID语法,可将视图置手动置为无效手动将视图无效后,可对下层对象并发做DDL操作,避免并发执行锁等待时间长问题
后续实现实现对于无效视图的刷新新增线程,尝试将视图的无效状态刷新为有效


3.1 底层对象改变,视图置为无效

适用版本:【8.1.0(及以上)】

为保证实现底层对象和视图之间的解耦且视图定义可以存在,在打开视图解耦后,对视图所依赖的底层对象做以下DDL操作,

  • ATLER TABLE DROP COLUMN

  • ATLER TABLE ADD COLUMN(只有视图依赖对象的RECORD类型时)

  • ATLER TABLE COLUMN TYPE

  • ALTER TABLE/VIEW SET SCHEMA

  • RENAME COLUMN/TABLE

  • DROP TABLE

  • CREATE OR REPLACE VIEW/FUCNTION

需要将视图依赖关系链中的所有视图标识为无效状态,其定义在pg_rewrite系统表中的ev_enabled字段('O’为正常,'D’为无效),该标识用于以后续对无效视图的处理。

以ATLER TABLE DROP COLUMN为例,新建视图v1-v5,依赖关系如下:

postgres=# select * from gs_view_dependency_path order by objname;
 objschema | objname | refobjschema | refobjname |                path
-----------+---------+--------------+------------+-------------------------------------
 public    | v1      | public       | t1         | public.t1
 public    | v2      | public       | t1         | public.t1
 public    | v3      | public       | v1         | public.t1 -> public.v1
 public    | v4      | public       | v3         | public.t1 -> public.v1 -> public.v3
 public    | v4      | public       | v2         | public.t1 -> public.v2
 public    | v5      | public       | v3         | public.t1 -> public.v1 -> public.v3
(6 rows)

执行ATLER TABLE DROP COLUMN后,视图状态结果如下:

postgres=# select * from gs_view_invalid;
    oid     | schemaname | viewname | viewowner |                        definition                        | validtype
------------+------------+----------+-----------+----------------------------------------------------------+-----------
 2147483676 | public     | v1       |   user1   | SELECT a, b, c FROM public.t1;                           | invalid
 2147483684 | public     | v3       |   user1   | SELECT a, b, c FROM public.v1;                           | invalid
 2147483688 | public     | v5       |   user1   | SELECT a, b, c FROM public.v3;                           | invalid
 2147483680 | public     | v2       |   user1   | SELECT a, b, c FROM public.t1;                           | invalid
 2147483692 | public     | v4       |   user1   | SELECT  * FROM public.v2 UNION SELECT  * FROM public.v3; | invalid
(5 rows)

当底层对象发生改变时,上层视图均会置为无效的状态。如果在依赖关系中,中间的视图发生变化,只有其上层视图会被置为无效,也就是说,当该视图无效时,其上层视图也必然是无效的。

3.2 手动执行SET INVALID, 视图置为无效

适用版本:【8.3.0(及以上)】

我们在业务中经常存在这种场景,一个视图依赖于两个及以上底层对象,此时在对底层对象分别做DDL操作时,因为都需要将上层视图置为无效状态,所以会导致另外一个对象锁等待周期过长的问题,无法并发执行。以下图所示场景为例:

postgres=# select * from gs_view_dependency_path;
 objschema | objname | refobjschema | refobjname |   path
-----------+---------+--------------+------------+-----------
 public    | v1      | public       | t2         | public.t2
 public    | v1      | public       | t1         | public.t1
(2 rows)

alter table t1 drop column需要将view1置为无效,alter table t2 drop column也需要将view1置为无效,此时置为无效需要修改pg_rewrite系统表,因此对视图持八级锁,两个alter table操作只能有一个持八级锁,另外一个就会等待。如果持锁的alter table持锁周期过长,另外一个就无法执行。

因此我们提供了一个语法

ALTER VIEW [ IF EXISTS ] view_name SET INVALID (暂不支持)
将本视图及上层、下层视图均置为无效

ALTER VIEW [ IF EXISTS ] ONLY view_name SET INVALID
将本视图及上层视图置为无效

将视图置为无效之后,对于底层对象执行DDL操作,对view1将不会持有八级锁,而是持有一级锁,因此上述两条DDL可以同时执行,持锁行为如下:

postgres=# START TRANSACTION;
START TRANSACTION
postgres=# ALTER TABLE T1 DROP COLUMN a;
ALTER TABLE
postgres=# SELECT locktype, relation::regclass, mode, granted FROM pg_locks WHERE locktype = 'relation' AND pid = pg_backend_pid() ORDER BY relation;
 locktype | relation |        mode         | granted
----------+----------+---------------------+---------
 relation | pg_locks | AccessShareLock     | t
 relation | t1       | AccessExclusiveLock | t
 relation | v1       | AccessShareLock     | t
(3 rows)

3.3 无效视图使用,视图刷新有效

对于无效视图处理方式,在不同版本之间行为不同,具体差异如下:

  1. 在8.1.0版本中,无效视图不能使用,需要先执行ALTER VIEW view REBUILD或CREATE OR REPALCE将视图刷新为有效状态在进行操作,过程中需要对该视图及下层无效视图加八级锁;

  2. 在8.1.1版本中,无效视图查询时同时尝试刷新视图,内部调用ALTER VIEW ONLY view REBUILD语法,对本视图及下层视图做重建,重建过程中对该视图及下层无效视图加八级锁后更新系统表;

  3. 在8.2.1版本中,无效视图做本地展开(相当于将视图作为一个子查询),本地重建时对该视图及下层视图持一级锁且不更新系统表,视图可正常使用

以DROP TABLE t1 + CREATE TABLE t1为例,对8.2.1版本中视图解耦功能简单使用做简要介绍:

定义基表和视图,此时视图为有效状态:

postgres=# \d+ v1
                   View "public.v1"
 Column |  Type   | Modifiers | Storage | Description
--------+---------+-----------+---------+-------------
 a      | integer |           | plain   |
 b      | integer |           | plain   |
 c      | integer |           | plain   |
View definition:
 SELECT  *
   FROM t1;

删除基表,视图自动置为无效,此时查询视图,视图展开报基表不存在:

postgres=# drop table t1;
DROP TABLE
postgres=# \d+ v1
               Invalid View "public.v1"
 Column |  Type   | Modifiers | Storage | Description
--------+---------+-----------+---------+-------------
 a      | integer |           | plain   |
 b      | integer |           | plain   |
 c      | integer |           | plain   |
View definition:
SELECT a, b, c FROM public.t1;

postgres=# select * from v1;
ERROR:  relation "public.t1" does not exist
CONTEXT:  The view public.v1 is invalid, Please check view definition and execute CREATE OR REPLACE view.

重建基表,查询视图,视图可正常展开但视图依然为无效状态:

postgres=# create table t1(a int, b int, c int);
NOTICE:  The 'DISTRIBUTE BY' clause is not specified. Using round-robin as the distribution mode by default.
HINT:  Please use 'DISTRIBUTE BY' clause to specify suitable data distribution column.
CREATE TABLE
postgres=# select * from v1;
 a | b | c
---+---+---
(0 rows)

postgres=# \d+ v1
               Invalid View "public.v1"
 Column |  Type   | Modifiers | Storage | Description
--------+---------+-----------+---------+-------------
 a      | integer |           | plain   |
 b      | integer |           | plain   |
 c      | integer |           | plain   |
View definition:
SELECT a, b, c FROM public.t1;

执行ALTER VIEW ONLY view REBUILD后,视图刷新为有效

postgres=# alter view only v1 rebuild;
ALTER VIEW
postgres=# \d+ v1
                   View "public.v1"
 Column |  Type   | Modifiers | Storage | Description
--------+---------+-----------+---------+-------------
 a      | integer |           | plain   |
 b      | integer |           | plain   |
 c      | integer |           | plain   |
View definition:
 SELECT  *
   FROM t1;

postgres=# select * from v1;
 a | b | c
---+---+---
(0 rows)


查询视图状态、依赖关系相关视图如下

  • gs_view_dependency_basic:获取所有依赖于表的所有视图,包括常规表/临时表/unlogged表/外表/分区表

postgres=# select * from gs_view_dependency_basic;
  objectid  | objschema | objname | refobjschema | refobjectid | refobjname | relobjkind
------------+-----------+---------+--------------+-------------+------------+------------
 2147483648 | public    | v2      | public       |  2147483660 | t1         | r
 2147483666 | public    | v1      | public       |  2147483663 | t2         | r
 2147483666 | public    | v1      | public       |  2147483660 | t1         | r
 2147483652 | public    | v3      | public       |  2147483648 | v2         | v
 2147483648 | public    | v2      | public       |  2147483663 | t2         | r
(5 rows)

  • gs_view_dependent_self:获取对象依赖于对象的列及依赖对象类型

postgres=# select * from gs_view_dependent_self;
 objschema | objname | refobjschema | refobjname | relobjkind | col
-----------+---------+--------------+------------+------------+-----
 public    | t3      | public       | t3         | r          |   1
 public    | t3      | public       | v4         | v          |   1
(2 rows)

  • gs_view_dependency:获取视图依赖的对象以及依赖对象的类型

postgres=# select * from gs_view_dependency;
 objschema | objname | refobjschema | refobjname | relobjkind
-----------+---------+--------------+------------+------------
 public    | v1      | public       | t2         | r
 public    | v1      | public       | t1         | r
 public    | v2      | public       | t1         | r
 public    | v3      | public       | v2         | v
 public    | v2      | public       | t2         | r
(5 rows)

  • gs_view_dependency_path:获取视图的依赖路径

postgres=# select * from gs_view_dependency_path;
 objschema | objname | refobjschema | refobjname |          path
-----------+---------+--------------+------------+------------------------
 public    | v3      | public       | v2         | public.t1 -> public.v2
 public    | v3      | public       | v2         | public.t2 -> public.v2
 public    | v2      | public       | t2         | public.t2
 public    | v1      | public       | t2         | public.t2
 public    | v2      | public       | t1         | public.t1
 public    | v1      | public       | t1         | public.t1
(6 rows)

  • gs_view_extend:查询所有的视图及其定义和视图状态(invalid/valid)

postgres=# select * from gs_view_extend;
    oid     | schemaname | viewname | viewowner |                        definition                        | validtype
------------+------------+----------+-----------+----------------------------------------------------------+-----------
 2147483666 | public     | v1       |   user1   | SELECT  * FROM public.t1 UNION SELECT  * FROM public.t2; | invalid
 2147483648 | public     | v2       |   user1   | SELECT  * FROM t1 UNION SELECT  * FROM t2;               | valid
(2 rows)

  • gs_view_invalid:查询无效的视图及其定义和视图状态

postgres=# select * from gs_view_invalid;
    oid     | schemaname | viewname | viewowner |                        definition                        | validtype
------------+------------+----------+-----------+----------------------------------------------------------+-----------
 2147483666 | public     | v1       |   user1   | SELECT  * FROM public.t1 UNION SELECT  * FROM public.t2; | invalid
(1 row)


本文详细介绍了GaussDB(DWS)视图解耦功能的发展历程以及现有所支持的操作,在总结现在已有的功能的过程中,详细阐释了视图有效状态和无效状态切换时的持锁情况及行为逻辑,展示了视图相关的系统视图作用和效果,对比了友商与我们之间的行为差异。




往期精彩回顾



恭喜!大数据“星河”标杆案例奖+



戳阅读原文,直达GaussDB(DWS)开发者平台

文章转载自GaussDB DWS,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论