#其他模式对象

# 视图

用户可以将一个使用频率较高的查询语句定义为一个持久化的对象,该持久化对象称为视图,为视图提供原始数据的表称为基表。

通过查询视图代替原来的查询表,可简化SQL语句编写。

假设公司EMPLOYEE表包含所有员工个人信息,DEPARTMENT表记录所有部门信息。用户如需查询公司的所有员工个人信息及其所属部门信息(部门名称和主管名称),要求用户能清楚地知道员工个人信息、部门信息分别存储在哪张表中,并且需要了解表与表之间的关联关系,而后再执行以下对相关表的查询语句:

SELECT E.ID, E.NAME, D.NAME, DM.NAME 
FROM EMPLOYEE E, DEPARTMENT D, EMPLOYEE DM 
WHERE E.DEPARTMENT_ID = D.ID AND D.MNGR_ID = DM.ID;

数据库管理员相较于其他用户,通常更熟悉各个表的信息、关联关系等。若数据库管理员事先为上述查询创建一个视图:

CREATE OR REPLACE VIEW EMPLOYEE_INFO(EMPLOYEE_ID, EMPLOYEE_NAME, DEPARTMENT, DEPARTMENT_MANAGER) 
AS SELECT E.ID, E.NAME, D.NAME, DM.NAME 
FROM EMPLOYEE E, DEPARTMENT D, EMPLOYEE DM 
WHERE E.DEPARTMENT_ID = D.ID AND D.MNGR_ID = DM.ID;

当用户需要查询所有员工信息时,只需执行以下对视图的查询语句即可,操作更易效率更高。

SELECT * FROM EMPLOYEE_INFO;

在上述示例中,EMPLOYEE_INFO视图并不存储任何数据,其数据完全来自EMPLOYEE表和DEPARTMENT表。

# 视图的作用

  • 简化查询

    使用视图代替使用频率较高的查询,特别是复杂查询,可以降低用户编写SQL的复杂度。编写执行效率高的SQL(SQL语句优化)对于数据库使用者的专业能力要求较高。对于复杂的查询语句,可以先由专业能力更高的用户(例如数据库管理员)将其创建为视图供其他用户使用。因此,使用视图可以有效减少用户优化SQL的代价,降低使用难度。

  • 解耦合

    在保证视图列的名称、数据类型不变的前提下,修改基表其他元素的定义不影响视图的正常使用。业务系统升级时,可以相对自由地调整基表的定义(例如添加列)而不改变视图,或可以通过CREATE OR REPLACE VIEW轻松地更新视图所替代的查询语句。因此,使用视图可以在一定程度上解耦数据库设计与应用程序开发。

  • 权限隔离

    视图作为一个独立的对象,用户在视图上的权限与在基表上的权限是分离的。创建视图的用户必须具备基表的DML权限,但使用视图的用户无需具备基表的DML权限,只需具备视图的DML权限即可。因此,合理地设计视图列及权限,可以有效避免基表中的全部信息完全暴露给只需部分信息(视图列)的用户。

# 视图的依赖关系

视图用于代替查询,因此视图依赖查询中涉及的其他对象,包括基表、其他视图、自定义数据类型、自定义函数等。

在为某个查询语句创建视图时,需要编译该查询语句,确保该语法合法。在编译的过程中,会检测到视图所依赖的对象,默认情况下,依赖对象不存在时视图会创建失败。若创建视图时带了FORCE关键字,YashanDB将直接创建视图,不会要求视图依赖的对象已存在。

视图创建成功后,如果修改视图依赖的对象,例如增/删基表中视图所引用的列或DROP基表,将导致视图的状态无效。待再次在视图上执行DML语句时视图才会触发重编译,如果重编译检测到对应的查询语句仍然合法,视图恢复正常可用,否则将返回错误。

# 视图的访问

用户在创建视图时,数据库会在系统表中存储视图定义查询语句的原始文本、列等信息。

当SQL语句中涉及视图的查询时,数据库会先将原始SQL中的视图改写为一个子查询(该子查询即视图定义的查询语句),再解析改写后的SQL并生成执行计划。示例如下:

CREATE TABLE employees(id INT, name VARCHAR(32), LEVEL INT);
CREATE VIEW exports AS SELECT * FROM employees WHERE LEVEL > 3;
-- 执行以下select语句
SELECT * FROM exports;
-- 实际将改写为
SELECT exports.* FROM (SELECT id, name FROM employees WHERE LEVEL > 3) alias_exports;

如果视图依赖别的视图,那么在改写过程中,会将被依赖的视图也改写成其定义的查询语句,最终,视图的查询会被改写为查询它直接及间接依赖的基表。

改写后的SQL与原始SQL在语义上等价,但原始SQL和用户自行直接执行改写后的SQL,有以下区别:

  • 假设用户第一次执行查询视图的语句,查询成功。如果此时视图被删除(用户权限、相关基表等均未发生变化),该用户再次执行同一条查询视图的语句,会直接报错,不会转而查询基表。

  • 若当前用户只具备查询视图的权限并无查询视图基表的权限,查询视图会成功,但直接执行改成后的SQL会失败。

# 物化视图

通常视图只是用来代替原始的查询语句,并不存储任何数据,用户在查询视图时,数据库仍然要访问基表来获取原始数据,对于复杂的查询(例如多个表Join),查询时间可能很长。

物化视图与普通视图的区别是,物化视图会将查询返回的结果以表(称为物化视图的主表)的形式存储下来,将复杂的查询转化为单个表的查询,从而提高查询性能。

由于物化视图存储了数据,因此当基表的数据发生变化时,需要反向更新物化视图中的数据,这一过程称为物化视图的刷新。

用户可以根据业务对数据的变化的敏感度来选择物化视图刷新的频率。

示例场景1:银行的账户资产评级系统,需要对用户的信用等级、资产分布做出评估,并用一个物化视图来存储该结果。这个结果对数据变化的敏感度较低,不需要在用户执行每一笔交易后实时变更,可采用每天或每周定时刷新。因此用户可以将该物化视图设置为定时刷新。

示例场景2:银行的账户监管系统,需要对特殊账户的状态做实时监控,并用一个物化视图来存储该结果。由于银行需要及时响应账户挂失、销户等状态,因此,当特定操作发生后(挂失、销户等),物化视图的数据应立即刷新,因此该物化视图应设置为基表上DML提交时立即刷新。

用户也可以根据基表的数据变化规模来决定物化视图刷新的方式。

示例场景3:银行的账户资产评级系统,由于每天活跃的账户数量巨大,并且每个账户每天的交易量可能有多个,通过分析每一笔交易确定相应账户的资产状况变化情况耗时会比基于当前账户的状态对账户重新评级更大,因此该物化视图的数据适合全量刷新。

示例场景4:银行的账户监管系统,由于发生挂失、销户的账户比例非常小,如果全部刷新一遍所有账户信息,耗时可能很长,因此该物化视图更适合根据基表上账户的变更记录而增量刷新物化视图中对应的记录。

物化视图的意义如下:

  • 相比普通视图,物化视图存储了数据,可以将复杂的查询用查询单一主表代替,因此,对于执行过程中计算量很大的查询语句(例如包含窗口函数、聚集函数等),使用物化视图会有显著的性能提升。

  • 即使用户编写的SQL语句中使用物化视图,数据库在生成SQL执行计划时,仍然会考虑是否可以使用物化视图代替原始SQL中的部分,以提高性能。因此运维人员为某些耗时较长的SQL语句创建物化视图后,业务开发人员也无需改写业务中使用的SQL语句。

# 序列

序列(Sequence)是由数据库维护的一个自动生成整数序列的模式对象,通常用于为表生成主键。

# 序列的作用

例如,当新员工入职时,公司的人力资源需要登记员工信息,并为员工分配工号,那么在数据库上需要执行以下SQL语句:

INSERT INTO employees(id, ...) VALUES((SELECT MAX(id) FROM employee) + 1, ...);
-- 该语句相当于以下PL Block
DECLARE
  nextId INT;
BEGIN
  SELECT MAX(id) INTO nextId FROM employees;
  INSERT INTO employees(id, name, ...) VALUES(nextId + 1, 'Zhang San', ...);
END;
/  

以上语句,仅适用于公司人力资源部门只有专员负责登录新入职员工。

如果有一大批新员工同时入职,并且由多个人力资源专员同时登录入职时,就可能出现多个新员工分配到同一工号的问题。使用序列则可以规避该问题并简化操作:

CREATE SEQUENCE seq_empl_id;
INSERT INTO employees(id, name, ...) VALUES(seq_empl_id.NEXTVAL, 'Zhang San', ...);
-- 或者在建表时,将id列的default值指定为sequence
CREATE TABLE employees(id INT DEFAULT seq_empl_id.NEXTVAL, name VARCHAR(32), ...);
INSERT INTO employess(name, ...) VALUES('Zhang San', ...);

通过seq_emp_id.nextval获取序列seq_emp_id的下一个值,由于该值是由数据库系统自动维护,即使有多个会话同时请求,仍然可以保证每个会话获取到唯一的值。

序列具有以下特征:

  • 序列返回的结果是一个整数。

  • 用户可以设置序列的最大值、最小值、起始值,以及当序列取到最大值或最小值时是否循环取值。

  • 在不允许循环取值的情况下,一个序列取到的值永远是唯一的。

  • 序列返回的值可以是递增的,也可以是递减的,取决于创建序列时指定的步长(INCREMENT BY)。在不允许循环取值的情况下,序列取到的值单调性是确定的,不会时增时减。

# 序列值的缓存

在不允许循环取值的情况下,序列的主要作用是为系统提供唯一值,序列下次取值的起点会被写入系统表以持久化。如果频繁地从序列取值,会导致更新系统表的频率过高,影响性能,因此,YashanDB为序列的值提供了缓存机制。

CREATE SEQUENCE seq1 CACHE 20;
CREATE SEQUENCE seq2 CACHE 10000;
CREATE SEQUENCE seq3 NOCACHE;

以上三条语句分别创建了一个缓存为20、10000、无缓存的序列。

序列的缓存会在进程内为序列缓存连续若干个尚未取到的值,当缓存内的值取尽时,数据库会更新系统表,将下次取值的起点向后按缓存大小推进,并将缓存刷新为下一批值。

  • 缓存越大,更新系统表的次数越小,序列取值越快。

  • 如果数据库进程重启,缓存中尚未取到的值会丢失,缓存越小,更新系统表的频率越高,进程意外重启丢失的序列值越少。

  • 在共享集群部署中,可以指定序列每个实例可以独立管理序列。当缓存未用尽的情况下,终止进程,再次启动进程后,取到的序列值会与之前取到的不一致,但不会重复(循环取值的情况除外)。

  • 在共享集群部署中,所有实例维护的是同一份系统表,但每个实例都有独立的序列缓存。因此,可以保证在不循环取值的情况下,每个实例取到的序列值是唯一的,但不能保证不同实例依次取序列值得到的值是单调递增(或递减)的。如需保证不同实例之间的取值顺序,可以在创建序列时指定Order选项,此时序列将不会在各实例上产生缓存。

# 同义词

同义词是用户为一个模式对象起的别名。用户可以为表、视图、存储过程、自定义函数、程序包、序列和同义词创建同义词。

同义词具有以下特点:

  • 数据库会持久化同义词的定义,即同义词的名称以及它所代表的对象名称会被写入系统表,数据库重启后,同义词的定义依然有效。

  • 创建同义词时,数据库不会校验同义词所代表的对象是否真实存在,只在使用同义词时链接到该对象。

# 私有同义词

可以使用以下语句为table1创建一个私有同义词:

CREATE SYNONYM user1.syn1 FOR user2.table1;

通过这种方式创建出的同义词syn1属于模式user1(模式名可以不指定,此时数据库将自动将当前模式补充为同义词的模式),在其他模式下使用syn1时必须显式指定user1.syn1。

# 公共同义词

可以使用以下语句为table1创建一个公共同义词:

CREATE PUBLIC SYNONYM syn1 FOR user2.table1;

通过这种方式创建出的同义词属于PUBLIC模式,用户在使用syn1时,无需指定模式名。

公共同义词被广泛地用在YashanDB系统视图中。

例如,SYS模式下有USER_TABLES视图,会显示当前模式下所有表的信息。

如果用户user1想查询属于自己模式下的所有表,那么用户需要清楚地知道USER_TABLES视图属于SYS模式,并执行以下语句:

SELECT * FROM SYS.USER_TABLES;

由于YashanDB在初始化数据库阶段为SYS.USER_TABLES视图创建了公共同义词,因此user1可以执行以下语句来查询属于自己模式的表:

SELECT * FROM USER_TABLES;

# PL对象

PL对象包括自定义数据类型、自定义函数、存储过程和程序包,详情请查阅PL文档