1、触发器
可以把触发器看做是一种特殊的存储过程。触发器也是一种定义存放在数据字典中的模式对象,和存储过程一样,都包含声明和执行的 PL/sql 块。触发器和存储过程最大的不同在于调用方式,存储过程是由用户或应用程序来调用和执行的,而触发器由 Oracle 系统来调用的,当触发事件发生时,触发器即被触发,然后隐式运行。
一般来说,在数据库项目中得慎用触发器,尽量少用,因为过度的使用触发器会导致项目难以维护,用不好还会影响应用程序性能。因此本文只简要介绍触发器,并处给出创建触发器的语法和一个示例。如果你想要进一步了解 Oracle 中的触发器,请参考:
《Oracle Database Concepts: Triggers》
《Oracle Database PL/sql Language Reference: Using Triggers》
《Oracle Database PL/sql Language Reference: PL/sql Triggers》
《.Net程序员学用Oracle系列(25):触发器详解》。
创建触发器
语法:
CREATEORREPLACETRIGGERtrigger_name [BEFORE|AFTER][INSERT|DELETE|UPDATE]ONtable_name [FOREACHROW]DECLARE --定义变量BEGIN --触发器操作代码END;
示例:
CREATEORREPLACETRIGGERtrg_aiAFTERINSERTONdemo.t_courseFOREACHROWDECLARE v_todayDATE;BEGIN v_today:=SYSDATE; DBMS_OUTPUT.PUT_LINE('在'||TO_CHAR(v_today,'yyyy-mm-dd')||'添加了《'||:NEW.course_name||'》课程');END;
测试:
INSERTINTOt_course(course_id,course_name,course_desc)VALUES(5,'C/C++','计算机专业课程');
删除触发器
DROPTRIGGERtrg_ai;--删除名为trg_ai的触发器
2、作业
特殊说明:在上一版中,我将本节取名为任务,主要是本人之前看过的资料中大多将作业调度称之为任务,一开始我还纳闷儿呢,sql Server 中好像是叫作业啊,难道 Oracle 又制造了一个独特而又不遵守业界规范的东西?后来看过官方手册才知道,原来是被国内网友带偏了!
我仔细想过这个中文名的问题,后来我觉得还是叫作业比较妥当一些。因为管理数据库是要完成大量任务的,决定何时何处完成任务是需要计划的,这些都是由 Oracle Scheduler 来调度和完成的,那么由调度程序创建的 JOB 叫任务或计划都不太合理,因为容易让人混淆,而作业似乎是一个比较贴切的称呼。
2.1、作业调度功能和应用
一般关系型数据库都提供了作业调度功能,Oracle 也提供了可用于 PL/sql 的调度程序。调度程序使得数据库管理员和应用程序开发人员能够控制在数据库环境中何时何处发生各种任务。这些任务可以是耗时且复杂的,例如备份或夜间数据仓库加载和提取等,使有限的计算资源能够在竞争的作业中适当分配。
作业调度程序的最基本功能是能够安排作业在特定日期和时间或特定事件发生时运行。例如,需要让补丁应用于正在生产的数据库,为了尽量减少干扰,此任务需要在非高峰时段进行,当然可以让 IT人员在非高峰时段手动执行此任务;但为了降低运营成本,也可以使用作业调度程序轻松完成。
Oracle 先后提供了DBMS_JOB和DBMS_SCHEDULER两个作业调度程序包,后者比前者功能更强大、定义更灵活、也增强了与系统的交互性。Oracle 也建议从DBMS_JOB切换到DBMS_SCHEDULER,但仍然支持DBMS_JOB以实现向后兼容,且作业调度程序包共享相同的作业协调器。
可以在命令窗口输入show parameter job_queue_processes查看数据库中定时任务的最多并发数,一般设置为 10,如果设为 0,那么数据库定时作业是不会运行的。更改并发数的方法是alter system set job_queue_processes=10。
可以通过USER_JOBS视图来查询当前用户的作业调度情况。如下:
SELECTt.jobjobno,t.what,to_char(t.next_date,'yyyy-mm-ddhh24:mi:ss')next_date,t.intervalFROMUSER_JOBSt;
USER_JOBS视图各主要字段的含义说明,如下:
job:NUMBER 类型,任务的唯一编号。
what:VARCHAR2(4000),任务操作内容。
next_date:DATE 类型,下一次执行任务的时间。
interval:VARCHAR2(200),任务执行时间间隔。
log_user:提交任务的用户。
priv_user:赋予任务权限的用户。
schema_user:对任务作语法分析的用户模式。
last_date:最后一次成功运行任务的时间。
last_sec:如hh24:mm:ss格式的last_date日期的小时,分钟和秒。
this_date:正在运行任务的开始时间,如果没有运行任务则为 null。
this_sec:如hh24:mm:ss格式的this_date日期的小时,分钟和秒。
2.2、通过 DBMS_JOB 来调度作业
实际项目开发中,多数作业调度都会通过后台代码来完成,只有个别情况下会使用数据库中的作业调度功能。在 Oracle 10g 中,用DBMS_JOB来管理作业是比较方便的,接下来就详细说说如何使用DBMS_JOB。
创建作业:
语法:
DBMS_JOB.SUBMIT( jobOUTBINARY_INTEGER,whatINVARCHAR2,NEXT_DATEINDATEDEFAULTSYSDATE,[next_date]INDATE,[interval]INVARCHAR2DEFAULT'NULL',[no_parse]INBOOLEANDEFAULTFALSE,[instance]INBINARY_INTEGERDEFAULTANY_INSTANCE,[force]INBOOLEANDEFAULTFALSE);
参数说明:
next_date:作业的下一次运行时间。可通过DBMS_JOB.NEXT_DATE(job,next_date)来修改。
interval:作业运行的时间间隔。可通过DBMS_JOB.INTERVAL(job,interval)来修改。
interval 参数设置案例:
每分钟(的0秒)执行一次:TRUNC(SYSDATE,''mi'') + 1/(24*60)
每小时(的0分0秒)执行一次:TRUNC(SYSDATE,''hh24'') + 1/24
每隔 7 天执行一次:TRUNC(SYSDATE) + 7 + 1/24
每天凌晨两点执行一次:TRUNC(SYSDATE) + 1 + 2/24
每周一凌晨 1 点执行一次:TRUNC(NEXT_DAY(SYSDATE,''monday'')) + 1/24
每月 5 号凌晨 1 点执行一次:TRUNC(LAST_DAY(SYSDATE)) + 5 + 1/24
每季度第一天凌晨 1 点执行一次:TRUNC(ADD_MONTHS(SYSDATE,3),''Q'') + 1/24
每年 1 月 1 号凌晨 1 点执行一次:ADD_MONTHS(TRUNC(SYSDATE,''yyyy''),12) + 1/24
不再运行该作业并删除它:NULL
每星期六、星期日早上6点10分:TRUNC(LEAST(NEXT_DAY(SYSDATE,''SATURDAY"),NEXT_DAY(SYSDATE,"SUNDAY")))+(6×60+10)/(24×60)
示例:
DECLARE jobnoNUMBER;BEGIN DBMS_JOB.SUBMIT(jobno,'sp_sync_staff90;',SYSDATE,'TRUNC(SYSDATE)+1+2/24');COMMIT;END;
删除作业:
语法:
DBMS_JOB.REMOVE(job);
启用作业:
语法:
DBMS_JOB.RUN(job);
禁用作业:
语法:
DBMS_JOB.BROKEN( jobINBINARY_INTEGER,--为true表示禁用任务,为false表示启用任务 brokenINBOOLEAN,[next_date]INDATEDEFAULTSYSDATE );
示例:
BEGIN DBMS_JOB.BROKEN(135,TRUE);--禁用job为135的任务 COMMIT;END;
想要了解更多有关作业调度的知识可参考:
《Oracle Database Administrator's Guide: Scheduler Concepts》
《Oracle Database Administrator's Guide: Using the Scheduler》
《Oracle Database Administrator's Guide: Moving fromDBMS_JOBto DBMS_SCHEDULER》
《Oracle Database PL/sql Packages and Types Reference: DBMS_JOB》
《Oracle Database PL/sql Packages and Types Reference: DBMS_SCHEDULER》
3、序列
可以把序列当成是数字工厂,因为它唯一的功能就是生产等间隔的数值。Oracle 中序列最典型的用法是生产主键的值。有 Oracle 开发经验的人应该都知道,Oracle 没有提供类似于 sql Server 或 MysqL 中自动增长列的功能。如果需要使用自动增长列,则可以通过序列来实现类似功能。
我共参与过两个基于 Oracle 开发的项目,但都不用 Oracle 的序列(一个用 C# 代码生成序列号,另一个直接用 GUID),以至于到目前为止我都还没有使用序列的实战经验。下面只简单介绍下我所了解的序列。
3.1、创建序列
创建序列的标准语法如下:
CREATESEQUENCEseq_name[INCREMENTBYn][STARTWITHn] [{MAXVALUEn/NOMAXVALUE}][{MINVALUEn/NOMINVALUE}] [{CYCLE/NOCYCLE}][{CACHEn/NOCACHE}];
语法选项说明:
INCREMENT BY n:n 表示序列中连续两个值之间的间隔,也称作步长。如果 n 是正数则表示递增,如果 n 是负数则表示递减,默认是 1。
START WITH n:n 表示序列的起始值,即序列的第一个值,默认是 1,递增时 n 是 minValue,递减时 n 是 maxValue。
MAXVALUE n:n 表示序列的最大值,也可以选择 NOMAXVALUE,即不设置最大值,默认是 999999999999999999999999999。
MINVALUE n:n表示序列的最小值,也可以选择 NOMINVALUE,即不设置最小值,默认是 1。
CYCLE和NOCYCLE分别表示当序列的值达到极限值后循环取值和不循环取值。
CACHE n:n 定义存放序列的内存块的大小,默认为 20(个数字)。NOCACHE 表示不对序列进行内存缓冲。将序列 CACHE(预先生成一部分序列号,放入到内存中)到内存中,可以加速对序列的访问。
3.2、使用序列 & 删除序列
使用序列
SELECTseq_name.CURRVALFROMDUAL;--获得指定序列的当前值SELECTseq_name.NEXTVALFROMDUAL;--获得指定序列的下一个值
注意(有以下情况之一时不能使用序列):
1、在 DELETE、SELECT、UPDATE 的子查询中。
2、在视图或物化事物的查询中。
3、SELECT 查询中使用了 DISTINCT 操作符。
4、SELECT 查询中有 GROUP BY 或 ORDER BY。
如果想查询一下数据中到底有那些序列,语法如下:
SELECT*FROMUSER_SEQUENCES;--当前用户所有序列
删除序列,示例:
DROPSEQUENCEseq_name;
4、连接
Oracle 中有个叫做 Database link 的东东,翻译成中文应该是数据库连接,为了称呼方便,下文统一称之为连接。在我跟公司一个技术专家对话时,对方提到“数据库连接”,于是我上网查了下:连接是定义一个数据库到另一个数据库的路径的对象,连接允许你查询远程表及执行远程程序。
连接有两种类型的,分别是公用的和私有的。一个公用数据库链接对于数据库中的所有用户都是可用的,而一个私有链接仅对创建它的用户可用。由一个用户给另外一个用户授权私有数据库链接是不可能的,一个数据库链接要么是公用的,要么是私有的。在任何分布式环境里,连接都是必要的。另外要注意的是连接是单向的连接。
4.1、创建连接
创建连接之前先得确定三件事,第一本地数据库和远程数据库之间的网络是可以正常连接的,第二创建连接的账号必须得有 CREATE DATABASE LINK 或 CREATE PUBLIC DATABASE LINK 的权限,第三用来登录到远程数据库的帐号必须得有 CREATE SESSION 权限。
实践告诉我,创建连接的正确语法如下:
CREATE[PUBLIC]DATABASELINKdblink_nameCONNECTTOuser_nameIDENTIFIEDBYuser_pwdUSING'connect_string';
其中connect_string有两种写法,示例:
示例(写法一):
CREATEPUBLICDATABASELINKdblink168 CONNECTTOoffice IDENTIFIEDBY123456USING'192.168.1.168:1521/orcl';
示例(写法二):
CREATEPUBLICDATABASELINKdblink168 CONNECTTOoffice IDENTIFIEDBY123456USING'(DESCRIPTION=( ADDRESS_LIST=(ADDRESS=(PROTOCOL=TCP)(HOST=192.168.1.168)(PORT=1521)) )( CONNECT_DATA=(SERVICE_NAME=orcl) ) )';
网上很多文章中给出的创建语法里都不包含指定密码这一项,还说如果不指定,则使用当前的用户名和口令登录到远程数据库,我反复实验了好多次,都是直接报语法错误。不过我倒是发现了一个特点,指定密码创建成功之后,再去查看连接定义的 sql,会发现密码指令项不见了。我猜应该是 Oracle 做了特殊的加密处理,所以如果你要创建连接,不必担心显示指定密码后别人会看到,造成安全泄漏。
4.2、使用连接 & 删除连接
使用连接
我觉得连接真正的魅力之一便是使用方便,无论增删改查那种语句,只需要在表名后面跟上 @dblink_name 就能操作远程数据库了。如要查询 168 上用户表中女员工的数量,示例:
SELECTCOUNT(1)FROMt_staff@dblink168tWHEREt.gender=0;
测试中我也发现一个小问题,假如我要查询 168 上的服务器时间,按理说写法应该如下:
SELECTSYSDATEFROMDUAL@dblink168;--结果显示出来的时间仍是本地数据库的服务器时间
如果为了命名更加统一,或者不想让对方知道 dblink 的名字,也可以通过视图或同义词包装一下,示例:
CREATEVIEWv_nameASSELECT*FROMtable_name@dblink_name;CREATESYNONYMtable_nameFORtable_name@dblink_name;
跟连接有关的几个视图,介绍如下:
SELECT*FROMDBA_DB_LINKS;--查询当前数据库实例中所有dblinkSELECT*FROMV$DBLINK;--查询当前数据库示例中正在打开状态的dblinkSELECT*FROMUSER_SYS_PRIVStWHEREt.privilegeLIKE'%LINK%';--查询跟dblink有关的系统权限
删除连接
对于非 PUBLIC 类型的连接,只有 owner 自己才能删除,非 PUBLIC 类型的连接没有这个要求。删除连接的示例:
DROP[PUBLIC]DATABASELINKdblink168;