5.9 同义词

同义词是对象的备选名称。如果对象存在同义词,那么任何SQL语句都能通过实际名称或通过同义词来访问该对象。同义词看上去似乎没有什么作用,然而实际情况并非如此。使用同义词意味着应用程序可以对任何用户起作用,而不考虑哪个模式拥有视图和表,甚至不考虑表驻留在哪个数据库中。分析如下语句:

        select * from hr.employees@prod;

发出该语句的用户必须知道员工表由数据库链接PROD标识的数据库中的HR模式拥有(不需要关心数据库连接的细节,它们是一种访问不是目前所登录数据库的其他数据库中对象的方式)。如果用如下语句创建公有同义词:

        create public synonym emp for hr.employees@prod;

那么所有用户都需要输入如下语句:

        select * from emp;

这个语句既提供了数据无关性,又提供了位置透明性。用户必须有访问底层对象的权限,才能成功使用基于同义词的引用。只要调整同义词,就可以在不修改代码的情况下重命名或重定位表和视图。

与SELECT语句一样,DML语句可以像访问同义词引用的对象一样访问同义词。

私有同义词是模式对象。它们要么必须在自己的模式中,要么必须用模式名限定。公有同义词的存在与模式无关。任何具有查看权限的用户都可以引用公有同义词,不需要用模式名限定。私有同义词必须在模式中有唯一的名称,而公有同义词可以与模式对象同名。当执行访问不带模式限定符的对象的语句时,Oracle会先在局部模式中查找对象,只有在局部模式中找不到时才会在公有模式中查找。因此在前面的示例中,如果用户碰巧拥有名为EMP的表,它看到的就是这个表——而不是公有同义词指向的名为EMP的表。

创建同义词的语法如下:

        CREATE [PUBLIC] SYNONYM synonym FOR object ;

需要为用户授予创建私有同义词的权限,并且进一步授予创建公有同义词的权限。通常,只有数据库管理员能够创建(或删除)公有同义词。这是因为是否存在公有同义词会影响每个用户。

考点:

“公有同义词”中的“公有”意味着它不是模式对象,因此不能用模式名做前缀。这并不意味着每个人都具有对公有同义词的访问权限。

删除同义词的语法如下:

        DROP [PUBLIC] SYNONYM synonym ;

如果同义词引用的对象(表或视图)被删除,同义词仍然存在。这时试图使用这样的同义词会返回一个错误。在这一方面,同义词的行为方式与视图相同。如果重新创建对象,那么在使用同义词前必须重新编译。与视图一样,在下次访问同义词时自动重新编译,也可以使用如下语句显式地完成编译:

        ALTER SYNONYM synonym COMPILE;

练习5-8 创建和使用同义词

在本练习中,将用HR模式中的对象创建和使用私有同义词。可以使用SQL*Plus或SQL Developer。

(1) 作为用户HR连接到数据库。

(2) 为练习5-7中创建的三个视图创建同义词:

        create synonym emp_s for emp_anon_v;
        create synonym dept_s for dept_anon_v;
        create synonym dsum_s for dep_sum_v;

(3) 确认同义词等同于底层对象:

        describe emp_s;
        describe emp_anon_v;

(4) 通过对同义词而不是视图运行练习5-7中的语句,来确认同义词有效(甚至到产生相同错误的程度):

        select * from dsum_s;
        insert into dept_s values   (99, 'Temp Dept',1800 );
        insert into emp_s values   (sysdate, 'AC_MGR',10000,0,99);
        update emp_s set salary=salary*1.1;
        rollback;
        select max(salaries / staff) from dsum_s;

(5) 删除两个视图:

        drop view emp_anon_v;
        drop view dept_anon_v;

(6) 查询基于已删除视图的复杂视图:

        select * from dep_sum_v;

可以注意到查询失败。

(7) 尝试重新编译被破坏的视图:

        alter view dep_sum_v compile;

此次编译尝试也会失败。

(8) 删除DEP_SUM_V视图:

        drop view dep_sum_v;

(9) 查询已删除视图的同义词:

        select * from emp_s;

该查询会失败。

(10) 重新编译被破坏的同义词:

        alter synonym emp_s compile;

注意,虽然这样做不会抛出错误,而是从第(9)步开始重新运行查询。该同义词无疑仍然处于破坏状态。

(11) 通过删除同义词进行整理:

        drop synonym emp_s;
        drop synonym dept_s;
        drop synonym dsum_s;

5.10 序列

序列是生成唯一整数值的结构。由于只有一个会话能够读取下一个值,因此强制该值递增。这是序列化的要点,因此生成的每个值都将是唯一的。

序列是用来生成主键的宝贵工具。很多应用程序都需要自动生成主键值。日常业务数据处理的一个示例是客户编号或订单编号:业务分析师会指出,每个订单必须有唯一的编号,编号应连续地递增。其他应用程序可能没有这样的行规要求,但是仍然需要序列来实施关系完整性。以电话账单系统为例:在行规中的唯一标识符是电话号码(字符串),一次电话呼叫的标识符将是主叫电话号码和通话开始时间(时间戳)。这些数据类型太复杂,对于通过电话交换系统的大流量的主键来说,没必要采用这么复杂的类型。尽管使用这些数据类型也能够记录信息,但是使用简单的数值列来定义主键和外键要快得多。这些列中的值可以作为序列的基础。

序列机制与表、行锁机制及提交或回滚过程无关。这意味着序列每分钟能发出数千个唯一值,这比任何涉及从表中选择列、更新列并提交修改的方法快得多。

图5-12显示了两个从一个序列SEQ1中选择值的会话。

图5-12 两个会话并发使用序列

注意,图5-12中SEQ1.NEXTVAL的每个选择项生成一个唯一数值。这些数值按选择的时间顺序连续发出,数值会全局地递增,而不是仅在一个会话中递增。

5.10.1 创建序列

创建序列的完整语法如下:

        CREATE SEQUENCE [schema.]sequencename
        [INCREMENT BY number]
        [START WITH number]
        [MAXVALUE number | NOMAXVALUE]
        [MINVALUE number | NOMINVALUE]
        [CYCLE | NOCYCLE]
        [CACHE number | NOCACHE]
        [ORDER | NOORDER] ;

可以看出,序列的创建可以非常简单。例如,图5-12中使用的序列是用如下语句创建的:

        create sequence seq1;

各选项如表5-2所示。

表5-2 创建序列的选项

对INCREMENT BY、START WITH,以及MAXVALUE或MINVALUE的适当设置取决于业务分析师。

CYCLE很少使用,因为它使序列发出重复值。如果用序列来生成主键值,那么只有当数据库中存在删除原有行比序列重新发出数值快得多的例程时,CYCLE才有意义。

缓存序列值对于性能至关重要。从序列中选择是应用程序代码中串行化的要点:一次只能有一个会话产生这个序列值。这种机制非常有效率:相比于先锁住行,再更新行,然后用COMMIT解锁行,使用这种机制会快很多。但是即便如此,从序列中选择仍然会造成会话之间的资源争用。CACHE关键字通知Oracle在缓存中预先生成序号。这意味着如果需要根据要求生成序号,则可以更快速地发出这些序号。

提示:

要缓存的默认序号个数是20。经验表明这个数量并不够。如果应用程序每秒钟从序列中选择10次,那么要将缓存值设置为50 000。不要羞于承认这一点。

5.10.2 使用序列

为使用序列,会话可以用强制序列递增的伪列NEXTVAL选择下一个值,也可以用伪列CURRVAL选择发送给该会话的上一个(或“当前”)值。NEXTVAL将是全局唯一值:选择它的每个会话的每个SELECT会得到不同的、递增的值。直到再次选择NEXTVAL前,CURRVAL将是某个会话的常量。无法找出序列发出的上一个值是什么值:可以使用NEXTVAL递增来获得下一个值,也可以使用CURRVAL来重新调用发送给会话的上一个值,但是无法找到发出的上一个值。

考点:

序列的CURRVAL是发送给当前会话的上一个值,不一定是发出的上一个值。直到选择了NEXTVAL后才能选择CURRVAL。

序列的一种典型用途是用于主键值。本例使用序列CUST_SEQ生成唯一客户号,使用序列ORDER_SEQ生成唯一订单号,使用LINE_ SEQ生成该订单的行条目的唯一行号。先创建序列,这是只执行一次的操作:

        create sequence order_seq start with 10;
        create sequence line_seq start with 10;

然后插入订单,每个订单所在的行作为单个事务:

        insert into orders (order_id, order_date, customer_id)
        values (order_seq.nextval, sysdate, '1000' );
        insert into order_items (order_id, order_item_id, product_id)
        values (order_seq.currval, line_seq.nextval, 'A111');
        insert into order_items (order_id, order_item_id, product_id)
        values (order_seq.currval, line_seq.nextval, 'B111');
        commit;

第一条INSERT语句生成了一个订单,它具有从客户编号1000的序列ORDER_SEQ得到的唯一订单号。第二条和第三条语句插入订单的两行:使用前面从ORDER_SEQ发出的订单号作为将这两行连接到订单的外键,并使用LINE_SEQ中的下一个值生成每一行的唯一标识符。最后,提交事务。

序列没有绑定到任何一个表。在前面的示例中,从技术上来说没有理由不使用一个序列来生成订单和行的主键值。

对于持久递增序列来说,COMMIT并不是必需的语句:序列递增是持久的操作,一旦发生就对其余部分可见,并且不能回滚。序列更新的发生与事务管理系统无关。由于这个原因,序列中总是存在间隔。如果数据库重启并且使用CACHE子句,那么间隔会比较大。当数据库关闭时,所有已生成并缓存但还没有发出的数值会丢失。下次重启时,序列的当前值是上次生成的数值,而不是上次发出的数值。因此,如果使用默认的CACHE 20,那么每次关闭/启动会丢失20个数值。

如果业务分析师指出序列中不能有间隔,那么必须使用另一种生成唯一数值的方式。对于前面生成订单的示例,当前订单号可以存储在该表中,并将它初始化为10:

        create table current_on(order_number number);
        insert into current_on values(10);
        commit;

然后创建订单的代码将变成:

        update current_on set order_number=order_number + 1;
        insert into orders (order_number, order_date, customer_number)
        values ((select order_number from current_on), sysdate, '1000');
        commit;

作为一种生成唯一订单号的方式,当然可以采用这样的代码,因为订单号的递增在插入订单的事务中,所以必要时可以使用插入回滚它:除非故意删除订单,否则订单号中不会有间隔。但是,这样做的效率远不如使用序列,而且众所周知的是,像这样的代码会引起致命的争用问题。如果有很多会话试图锁定并递增包含当前编号的一行,那么当队列在等待轮到自己时,整个应用程序就会挂起。

创建和使用序列后,可以对序列进行修改。语法如下:

        ALTER SEQUENCE sequencename
        [INCREMENT BY number]
        [START WITH number]
        [MAXVALUE number | NOMAXVALUE]
        [MINVALUE number | NOMINVALUE]
        [CYCLE | NOCYCLE]
        [CACHE number | NOCACHE]
        [ORDER | NOORDER] ;

ALTER命令与CREATE命令基本相同,只有一点区别:ALTER命令不能设置起始值。如果要重启该序列,唯一的方法是删除并重新创建它。为调整默认缓存值以改进前面的订单条目示例的性能,可以使用如下代码:

        alter sequence order_seq cache 1000;

然而,如果希望将该序列重置为它的起始值,唯一的方法是先删除它:

        drop sequence order_seq;

然后创建该序列。

练习5-9 创建和使用序列

在本练习中,创建一些序列并使用它们。需要两个并发会话,可以使用SQL Developer或SQL*Plus。

(1) 在单独的会话中作为HR登录到数据库两次。将其中一次登录看成A会话,另一次看成B会话。

(2) 在A会话中,创建如下所示的序列:

        create sequence seq1 start with 10 nocache maxvalue 15 cycle;

NOCACHE的使用会降低性能。如果指定了MAXVALUE,那么有必要用CYCLE防止到达MAXVALUE时出错。

(3) 在适当的会话中按正确的顺序执行表5-3中的命令,以观察NEXTVAL和CURRVAL的使用以及序列的循环。

表5-3 执行命令

(4) 创建一个带主键的表:

    create table seqtest(c1 number, c2 varchar2(10));
    alter table seqtest add constraint seqtest_pk primary key (c1);

(5) 创建一个序列来生成主键值:

    create sequence seqtest_pk_s;

(6) 在A会话中,向新表中插入一行并提交:

    insert into seqtest values (seqtest_pk_s.nextval, 'first');
    commit;

(7) 在B会话中,向新表中插入一行且不提交:

    insert into seqtest values (seqtest_pk_s.nextval, 'second');

(8) 在A会话中,插入第三行并提交:

    insert into seqtest values (seqtest_pk_s.nextval, 'third');
    commit;

(9) 在B会话中,回滚第二个插入:

    rollback;

(10) 在B会话中,查看表的内容:

    select * from seqtest;

这就演示了在事务控制机制外部递增序列并立即发布下一个值。

(11) 整理表和序列:

        drop table seqtest;
        drop sequence seqtest_pk_s;
        drop sequence seq1;

(12) 使用SQL Developer或SQL*Plus连接到HR模式,并创建三个将在后续练习中使用的序列。

        create sequence prod_seq;
        create sequence cust_seq;
        create sequence order_seq;

5.11 本章知识点回顾

分类主要的数据库对象

● 有些对象包含数据,主要是表和索引。

● 编程对象(如存储过程和函数)是可执行的代码。

● 视图和同义词是能够访问其他对象的对象。

● 表是保存使用列定义的行的二维结构。

● 表在模式中。模式名和表名形成唯一标识符。

列举列可用的数据类型

● 最常见的字符数据类型有VARCHAR2、NUMBER和DATE。

● 还有其他许多数据类型。

创建简单的表

● 可以从头开始创建表或者使用子查询创建表。

● 创建之后,可以添加、删除或者修改列定义。

● 表定义可以包含列的默认值。

创建和使用临时表

● 只有插入行的会话才能访问临时表中的行。

● 针对临时表的DML不生成重做数据。

● 临时表只存在于会话的PGA或临时段中。

● 临时表只在会话期间或事务期间(具体取决于创建方式)保存行。

索引

● 要实施唯一约束和主键约束,就必须使用索引。

● B*树索引不包括NULL,但位图索引包含NULL。

● B*树索引可为唯一索引,也可为非唯一索引,这些将决定是否接受重复键值。

● B*树索引适用于基数大的列,而位图索引适用于基数小的列。

● 位图索引可以采用复合形式,可以基于函数,也可以降序排列。B*树索引可以是唯一的、压缩的和反向键。

约束

● 可以在创建表时定义约束,也可以在随后添加。

● 可以与列一起定义约束,或者在列之后的表级别定义约束。

● 表级别约束可能比内联定义的约束更复杂。

● 表只能有一个主键,但可以有许多唯一键。

● 主键的功能等同于UNIQUE加上NOT NULL。

● 唯一约束不阻止多个空值的插入。

● 外键约束定义表之间的关系。

视图

● 简单视图有一个明细表(或基表),既不使用函数也不使用聚合。

● 复杂视图可以基于任何SELECT语句,不管多么复杂。

● 视图是模式对象。要在另一个模式中使用视图,必须用模式名限定视图名。

● 可以像查询表一样查询视图。

● 可以将视图联接到其他视图或者联接到表,它们可以被聚合,在有些情况下它们可以接受DML语句。

● 视图仅作为数据字典结构存在。每当查询视图时,必须运行底层SELECT语句。

同义词

● 同义词是视图或表的另一个名称。

● 私有同义词是模式对象;公有同义词存在于用户模式之外,不需要指定模式名作为限定符就可以使用。

● 同义词与视图和表共享相同的名称空间,因此可以与它们互换使用。

序列

● 序列生成唯一值——除非指定了MAXVALUE或MINVALUE和CYCLE。

● 递增序列不需要提交,不能回滚。

● 任何会话都能通过读取它的下一个值来递增序列。可以获得上次发送给会话的值,但不能获得上次发出的值。