GaussDB(DWS)视图解耦与重建功能介绍
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 无效视图使用,视图刷新有效
对于无效视图处理方式,在不同版本之间行为不同,具体差异如下:
- 在8.1.0版本中,无效视图不能使用,需要先执行ALTER VIEW view REBUILD或CREATE OR REPALCE将视图刷新为有效状态在进行操作,过程中需要对该视图及下层无效视图加八级锁;
- 在8.1.1版本中,无效视图查询时同时尝试刷新视图,内部调用ALTER VIEW ONLY view REBUILD语法,对本视图及下层视图做重建,重建过程中对该视图及下层无效视图加八级锁后更新系统表;
- 在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
- 点赞
- 收藏
- 关注作者
评论(0)