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

举报
半岛里有个小铁盒 发表于 2024/02/25 17:04:13 2024/02/25
【摘要】 GaussDB(DWS)数仓产品内部使用对象标识符(oid)来保存对象之间的引用关系,这使得视图在定义时就绑定了其依赖的基表的oid。如果要删除字段或整个表,就需要连同其关联的视图一起使用cascade关键字删除,表修改完成后再重建各级视图,这就给用户的使用增加了很大的工作量。为了解决这一问题,GaussDB(DWS) 在8.1.0版本实现了视图的解耦,8.1.1版本在此基础上又实现了自动刷新,8

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

1.前言

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

GaussDB(DWS)数仓产品内部使用对象标识符(oid)来保存对象之间的引用关系,这使得视图在定义时就绑定了其依赖的基表的oid。如果要删除字段或整个表,就需要连同其关联的视图一起使用cascade关键字删除,表修改完成后再重建各级视图,这就给用户的使用增加了很大的工作量。为了解决这一问题,GaussDB(DWS) 在8.1.0版本实现了视图的解耦,8.1.1版本在此基础上又实现了自动刷新,8.2.1版本实现了本地自动刷新,避免了自动刷新时持锁周期过长、持锁粒度过大的问题。本篇将带你了解GaussDB(DWS)视图解耦功能实现原理以及适用场景。

2.背景简介

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.功能详解

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)

4.相关视图

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

  • 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)
    

5.友商对比

在打开视图解耦后, GaussDB(DWS)的实现行为与友商基本一致,具体如下

依赖对象的行为 TeraData Oracle GaussDB DWS(视图解耦后的)
add column 查询不出新列 查询不出新列 查询不出新列
drop column 查询视图报错 查询视图报错 查视图报错
drop column 后添加列 查询视图成功 查询视图成功 查询视图成功
alter column type 正常查询 正常查询 正常查询
alter column name 查询的结果视图名与列名结果对应,不匹配报错 查询的结果视图名与列名结果对应,不匹配报错 查询的结果视图名与列名结果对应,不匹配报错
drop table后 create table列名一致、类型改变 正常查询 正常 查询 正常查询
drop table后 create table列名不一致 正常 查询顺序与视图初始定义一致 正常 查询顺序与视图初始定义一致 正常 查询顺序与视图定义一致
Rename 表名 查视图报错 查视图报错 查视图报错

6.总结

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

7.参考文献

1.GaussDB(DWS)视图解耦与自动重建功能介绍 https://bbs.huaweicloud.com/blogs/238425

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。