VIEW dependencies in Oracle、MySQL、PostGreSQL(数据库比较系列十一)
在有些程序员开发习惯中,喜欢为了应用代码的简洁或复用,而在数据库创建一个复杂关连查询的VIEW,甚至是VIEW套VIEW嵌套使用, 这里就有个问题如果上线后如发现依赖的表字段类型或长度不足时,修复一个view依赖的table列时发现在oracle、mysql、postgresql(本篇等同pg)中有不同的表现, 尤其是使用postgresql的用户需要格外注意, 因为pg 不允许直接修改, 学术派的严谨, 子之琼浆,彼之砒霜. 喜忧参半。
Oracle
SQL> CREATE TABLE t (id integer PRIMARY KEY); Table created. SQL> CREATE VIEW v AS SELECT * FROM t; View created. SQL> select status from user_objects where object_name='V'; STATUS ------- VALID SQL> DROP TABLE T; Table dropped. SQL> select status from user_objects where object_name='V'; STATUS ------- INVALID SQL> CREATE TABLE t (id integer PRIMARY KEY); Table created. SQL> select status from user_objects where object_name='V'; STATUS ------- INVALID SQL> select * from v; no rows selected SQL> select status from user_objects where object_name='V'; STATUS ------- VALID SQL> alter table t add name varchar2(20); Table altered. SQL> select status from user_objects where object_name='V'; STATUS ------- VALID SQL> desc v Name Null? Type ------------------------------- -------- ---------------------------- 1 ID NOT NULL NUMBER(38) SQL> desc t Name Null? Type ------------------------------- -------- ---------------------------- 1 ID NOT NULL NUMBER(38) 2 NAME VARCHAR2(20) SQL> @ddl v PL/SQL procedure successfully completed. DBMS_METADATA.GET_DDL(OBJECT_TYPE,OBJECT_NAME,OWNER) -------------------------------------------------------------------------------- CREATE OR REPLACE FORCE NONEDITIONABLE VIEW "SYS"."V" ("ID") AS SELECT "ID" FROM t; SQL> alter table t modify id number(10); Table altered. SQL> select * from v; no rows selected SQL> desc v; Name Null? Type ------------------------------- -------- ---------------------------- 1 ID NOT NULL NUMBER(10) SQL> alter table t rename to t100; Table altered. SQL> select status from user_objects where object_name='V'; STATUS ------- INVALID SQL> select * from v; select * from v * ERROR at line 1: ORA-04063: view "SYS.V" has errors
Note:
在oracle中VIEW依赖的TABLE发生DDL后会变为INVALID状态,在下次调用时自动recompile, 如果应用来的表名,列名存在可以正常查询。修改依赖列长度正常修改,在依赖表rename后VIEW无法查询.
查找依赖关系
SQL> desc dba_dependencies Name Null? Type ------------------------------- -------- ---------------------------- 1 OWNER NOT NULL VARCHAR2(128) 2 NAME NOT NULL VARCHAR2(128) 3 TYPE VARCHAR2(19) 4 REFERENCED_OWNER VARCHAR2(128) 5 REFERENCED_NAME VARCHAR2(128) 6 REFERENCED_TYPE VARCHAR2(19) 7 REFERENCED_LINK_NAME VARCHAR2(128) 8 DEPENDENCY_TYPE VARCHAR2(4) SQL> @dep % v % % OWNER DEPENDENT_NAME DEPENDENT_TY REF_OWNER REF_NAME REF_TYPE DEP_ ---------------- ------------------------------ ------------ ---------------- ------------------------------ ------------ ---- SYS V VIEW SYS T100 TABLE HARD SQL> l 1 select 2 owner dep_owner, 3 name dep_name, 4 type dep_type, 5 referenced_owner dep_referenced_owner, 6 referenced_name dep_referenced_name, 7 referenced_type dep_referenced_type, 8 dependency_type dep_dependency_type 9 -- 10 --from dba_dependencies where owner like '&1' and referenced_owner like '&2' 11 --from dba_dependencies where owner like '&1' and name like '&2' 12 -- 13 from 14 dba_dependencies 15 where 16 lower(owner) like lower('&1') 17 and lower(name) like lower('&2') 18 and lower(referenced_owner) like lower('&3') 19* and lower(referenced_name) like lower('&4') SQL>
MySQL
MYSQL_root@localhost [anbob]> CREATE TABLE t (id integer PRIMARY KEY); Query OK, 0 rows affected (0.05 sec) MYSQL_root@localhost [anbob]> CREATE VIEW v AS SELECT * FROM t; Query OK, 0 rows affected (0.02 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> select * from tables where table_schema='anbob' and TABLE_TYPE='VIEW' \G *************************** 1. row *************************** TABLE_CATALOG: def TABLE_SCHEMA: anbob TABLE_NAME: v TABLE_TYPE: VIEW ENGINE: NULL VERSION: NULL ROW_FORMAT: NULL TABLE_ROWS: NULL AVG_ROW_LENGTH: NULL DATA_LENGTH: NULL MAX_DATA_LENGTH: NULL INDEX_LENGTH: NULL DATA_FREE: NULL AUTO_INCREMENT: NULL CREATE_TIME: 2022-08-17 18:03:22 UPDATE_TIME: NULL CHECK_TIME: NULL TABLE_COLLATION: NULL CHECKSUM: NULL CREATE_OPTIONS: NULL TABLE_COMMENT: VIEW 1 row in set (0.00 sec) MYSQL_root@localhost [anbob]> DROP TABLE t; Query OK, 0 rows affected (0.04 sec) MYSQL_root@localhost [anbob]> select * from v; ERROR 1356 (HY000): View 'anbob.v' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them MYSQL_root@localhost [INFORMATION_SCHEMA]> select * from tables where table_schema='anbob' and TABLE_TYPE='VIEW' \G *************************** 1. row *************************** TABLE_CATALOG: def TABLE_SCHEMA: anbob TABLE_NAME: v TABLE_TYPE: VIEW ENGINE: NULL VERSION: NULL ROW_FORMAT: NULL TABLE_ROWS: NULL AVG_ROW_LENGTH: NULL DATA_LENGTH: NULL MAX_DATA_LENGTH: NULL INDEX_LENGTH: NULL DATA_FREE: NULL AUTO_INCREMENT: NULL CREATE_TIME: 2022-08-17 18:03:22 UPDATE_TIME: NULL CHECK_TIME: NULL TABLE_COLLATION: NULL CHECKSUM: NULL CREATE_OPTIONS: NULL TABLE_COMMENT: View 'anbob.v' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them 1 row in set, 1 warning (0.00 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> CREATE TABLE anbob.t (id integer PRIMARY KEY); Query OK, 0 rows affected (0.06 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> select * from tables where table_schema='anbob' and TABLE_TYPE='VIEW' \G *************************** 1. row *************************** TABLE_CATALOG: def TABLE_SCHEMA: anbob TABLE_NAME: v TABLE_TYPE: VIEW ENGINE: NULL VERSION: NULL ROW_FORMAT: NULL TABLE_ROWS: NULL AVG_ROW_LENGTH: NULL DATA_LENGTH: NULL MAX_DATA_LENGTH: NULL INDEX_LENGTH: NULL DATA_FREE: NULL AUTO_INCREMENT: NULL CREATE_TIME: 2022-08-17 18:03:22 UPDATE_TIME: NULL CHECK_TIME: NULL TABLE_COLLATION: NULL CHECKSUM: NULL CREATE_OPTIONS: NULL TABLE_COMMENT: VIEW 1 row in set (0.00 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> desc anbob.v -> ; +-------+------+------+-----+---------+-------+ | Field | Type | Null | Key | Default | Extra | +-------+------+------+-----+---------+-------+ | id | int | NO | | NULL | | +-------+------+------+-----+---------+-------+ 1 row in set (0.00 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> alter table anbob.t add name varchar(20); Query OK, 0 rows affected (0.09 sec) Records: 0 Duplicates: 0 Warnings: 0 MYSQL_root@localhost [INFORMATION_SCHEMA]> select * from tables where table_schema='anbob' and TABLE_TYPE='VIEW' \G *************************** 1. row *************************** TABLE_CATALOG: def TABLE_SCHEMA: anbob TABLE_NAME: v TABLE_TYPE: VIEW ENGINE: NULL VERSION: NULL ROW_FORMAT: NULL TABLE_ROWS: NULL AVG_ROW_LENGTH: NULL DATA_LENGTH: NULL MAX_DATA_LENGTH: NULL INDEX_LENGTH: NULL DATA_FREE: NULL AUTO_INCREMENT: NULL CREATE_TIME: 2022-08-17 18:03:22 UPDATE_TIME: NULL CHECK_TIME: NULL TABLE_COLLATION: NULL CHECKSUM: NULL CREATE_OPTIONS: NULL TABLE_COMMENT: VIEW 1 row in set (0.00 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> alter table anbob.t rename to t100; ERROR 1044 (42000): Access denied for user 'root'@'%' to database 'information_schema' MYSQL_root@localhost [INFORMATION_SCHEMA]> alter table anbob.t rename to anbob.t100; Query OK, 0 rows affected (0.20 sec) MYSQL_root@localhost [INFORMATION_SCHEMA]> select TABLE_SCHEMA,TABLE_NAME,TABLE_TYPE,TABLE_COMMENT from tables where table_schema='anbob' and TABLE_TYPE='VIEW' and table_comme *************************** 1. row *************************** TABLE_SCHEMA: anbob TABLE_NAME: v TABLE_TYPE: VIEW TABLE_COMMENT: View 'anbob.v' references invalid table(s) or column(s) or function(s) or definer/invoker of view lack rights to use them 1 row in set, 2 warnings (0.01 sec)
Note:
在MySQL中和oracle基本一样, 只是没有oracle的dba_object.status 是否invalid表示,而是在INFORMATION_SCHEMA.TABLE_COMMENT有invalid关键字错误。
PostgreSQL
[local]:5432 postgres@anbob=# CREATE TABLE t (id integer PRIMARY KEY); CREATE TABLE [local]:5432 postgres@anbob=# CREATE VIEW v AS SELECT * FROM t; CREATE VIEW [local]:5432 postgres@anbob=# DROP TABLE t; ERROR: cannot drop table t because other objects depend on it DETAIL: view v depends on table t HINT: Use DROP ... CASCADE to drop the dependent objects too. [local]:5432 postgres@anbob=# alter table t add name varchar(20); ALTER TABLE [local]:5432 postgres@anbob=# select * from v; id ---- (0 rows) [local]:5432 postgres@anbob=# \d v View "public.v" Column | Type | Collation | Nullable | Default --------+---------+-----------+----------+--------- id | integer | | | [local]:5432 postgres@anbob=# drop view v; DROP VIEW [local]:5432 postgres@anbob=# CREATE VIEW v AS SELECT * FROM t; CREATE VIEW [local]:5432 postgres@anbob=# \d v View "public.v" Column | Type | Collation | Nullable | Default --------+-----------------------+-----------+----------+--------- id | integer | | | name | character varying(20) | | | [local]:5432 postgres@anbob=# alter table t alter column name type varchar(40); ERROR: cannot alter type of a column used by a view or rule DETAIL: rule _RETURN on view v depends on column "name" [local]:5432 postgres@anbob=# [local]:5432 postgres@anbob=# create view vv as select * from v; CREATE VIEW [local]:5432 postgres@anbob=# alter table t alter column name type varchar(40); ERROR: cannot alter type of a column used by a view or rule DETAIL: rule _RETURN on view v depends on column "name" [local]:5432 postgres@anbob=# drop view v; ERROR: cannot drop view v because other objects depend on it DETAIL: view vv depends on view v HINT: Use DROP ... CASCADE to drop the dependent objects too. [local]:5432 postgres@anbob=# explain analyze select * from vv where id=1; QUERY PLAN ----------------------------------------------------------------------------------------------------------- Index Scan using t_pkey on t (cost=0.15..8.17 rows=1 width=28) (actual time=0.004..0.004 rows=0 loops=1) Index Cond: (id = 1) Planning Time: 0.082 ms Execution Time: 0.017 ms (4 rows) [local]:5432 postgres@anbob=# alter table t rename to t100; ALTER TABLE [local]:5432 postgres@anbob=# select * from v; id | name ----+------ (0 rows)
Note:
在PG中view对其使用的对象没有直接依赖关系:依赖对象实际上是视图的rewrite rule, 这增加了另一层间接性。 如果对view依赖的表或表列修改或删除时,需要删除view和重建,当然增加列并没有影响 , 报错中会提示表的直接依赖,但递归包含并没有, 可以想象如果在PG中大量使用view后期维护表时会有一些小麻烦,这也可以简单认为pg中没有失效对象,至少是view。
另外与oracle 和mysql不同的是,pg中依赖的是对象oid, 在table对象rename后,并不会影响view的使用,依赖的是OID ,而不是NAME.
[local]:5432 postgres@anbob=# alter table t100 add address text; ALTER TABLE [local]:5432 postgres@anbob=# alter table t100 drop column address; ALTER TABLE
检查依赖关系
[local]:5432 postgres@anbob=# \d pg_depend Table "pg_catalog.pg_depend" Column | Type | Collation | Nullable | Default -------------+---------+-----------+----------+--------- classid | oid | | not null | objid | oid | | not null | objsubid | integer | | not null | refclassid | oid | | not null | refobjid | oid | | not null | refobjsubid | integer | | not null | deptype | "char" | | not null |
classid存储包含依赖对象的目录表的对象 ID
objid存储依赖对象的ID
objsubid如果依赖项是针对列的,则存储列号
refclassid,refobjid和refobjsubid上面的三列一样,但是描述了依赖所引用的对象
deptype描述依赖的类型
查找表上的直接视图依赖项
[local]:5432 postgres@anbob=# SELECT v.oid::regclass AS view anbob-# FROM pg_attribute AS a -- columns for the table anbob-# JOIN pg_depend AS d -- objects that depend on the column anbob-# ON d.refobjsubid = a.attnum AND d.refobjid = a.attrelid anbob-# JOIN pg_rewrite AS r -- rules depending on the column anbob-# ON r.oid = d.objid anbob-# JOIN pg_class AS v -- views for the rules anbob-# ON v.oid = r.ev_class anbob-# WHERE v.relkind = 'v' -- only interested in views anbob-# -- dependency must be a rule depending on a relation anbob-# AND d.classid = 'pg_rewrite'::regclass anbob-# AND d.refclassid = 'pg_class'::regclass anbob-# AND d.deptype = 'n' -- normal dependency anbob-# AND a.attrelid = 't100'::regclass; view ------ v v (2 rows)
查找对表列的直接依赖关系
[local]:5432 postgres@anbob=# SELECT v.oid::regclass AS view anbob-# FROM pg_attribute AS a -- columns for the table anbob-# JOIN pg_depend AS d -- objects that depend on the column anbob-# ON d.refobjsubid = a.attnum AND d.refobjid = a.attrelid anbob-# JOIN pg_rewrite AS r -- rules depending on the column anbob-# ON r.oid = d.objid anbob-# JOIN pg_class AS v -- views for the rules anbob-# ON v.oid = r.ev_class anbob-# WHERE v.relkind = 'v' -- only interested in views anbob-# -- dependency must be a rule depending on a relation anbob-# AND d.classid = 'pg_rewrite'::regclass anbob-# AND d.refclassid = 'pg_class'::regclass anbob-# AND d.deptype = 'n' -- normal dependency anbob-# AND a.attrelid = 't100'::regclass anbob-# AND a.attname = 'name'; view ------ v (1 row)
递归查找所有依赖视图
[local]:5432 postgres@anbob=# WITH RECURSIVE views AS ( -- get the directly depending views SELECT v.oid::regclass AS view, v.relkind = 'm' AS is_materialized, 1 AS level FROM pg_depend AS d JOIN pg_rewrite AS r ON r.oid = d.objid JOIN pg_class AS v ON v.oid = r.ev_class WHERE v.relkind IN ('v', 'm') AND d.classid = 'pg_rewrite'::regclass AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'n' AND d.refobjid = 't100'::regclass UNION -- add the views that depend on these SELECT v.oid::regclass, v.relkind = 'm', views.level + 1 FROM views JOIN pg_depend AS d ON d.refobjid = views.view JOIN pg_rewrite AS r ON r.oid = d.objid JOIN pg_class AS v ON v.oid = r.ev_class WHERE v.relkind IN ('v', 'm') AND d.classid = 'pg_rewrite'::regclass AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'n' AND v.oid <> views.view -- avoid loop ) SELECT format('CREATE%s VIEW %s AS%s', CASE WHEN is_materialized THEN ' MATERIALIZED' ELSE '' END, view, pg_get_viewdef(view)) FROM views GROUP BY view, is_materialized ORDER BY max(level); format ---------------------------------- CREATE VIEW v AS SELECT t100.id,+ t100.name + FROM t100; CREATE VIEW vv AS SELECT v.id, + v.name + FROM v; (2 rows) [local]:5432 postgres@anbob=# WITH RECURSIVE views AS ( -- get the directly depending views SELECT v.oid::regclass AS view, v.relkind = 'm' AS is_materialized, 1 AS level, d.objid,d.refobjid::regclass FROM pg_depend AS d JOIN pg_rewrite AS r ON r.oid = d.objid JOIN pg_class AS v ON v.oid = r.ev_class WHERE v.relkind IN ('v', 'm') AND d.classid = 'pg_rewrite'::regclass AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'n' AND d.refobjid = 't1'::regclass UNION -- add the views that depend on these SELECT v.oid::regclass, v.relkind = 'm', views.level + 1, d.objid,d.refobjid::regclass FROM views JOIN pg_depend AS d ON d.refobjid = views.view JOIN pg_rewrite AS r ON r.oid = d.objid JOIN pg_class AS v ON v.oid = r.ev_class WHERE v.relkind IN ('v', 'm') AND d.classid = 'pg_rewrite'::regclass AND d.refclassid = 'pg_class'::regclass AND d.deptype = 'n' AND v.oid <> views.view -- avoid loop ) SELECT * FROM views; view | is_materialized | level | objid | refobjid ------+-----------------+-------+-------+---------- v1 | f | 1 | 26564 | t1 v2 | f | 2 | 26586 | v1 (2 rows) -- v1 base on t1, v2 base on v1.
— over —
reference TRACKING VIEW DEPENDENCIES IN POSTGRESQL
对不起,这篇文章暂时关闭评论。