2010年12月28日 星期二

make a new schema in Oracle

CREATE USER seasonal IDENTIFIED BY test;
grant resource, connect to seasonal;

2010年12月27日 星期一

Administration - Session

Show all connected users

select username
, sid || ',' || serial# "ID"
, status
, last_call_et "Last Activity"
from v$session
where username is not null
order by status desc
, last_call_et desc;

--------------------------------------------------

Time since last user activity

select username
, floor(last_call_et / 60) "Minutes"
, status
from v$session
where username is not null
order by last_call_et
--------------------------------------------------

Sessions sorted by logon time

select username
, osuser
, sid || ',' || serial# "ID"
, status
, to_char(logon_time, 'hh24:mi dd/mm/yy') login_time
, last_call_et
from v$session
where username is not null
order by login_time;
--------------------------------------------------

Show user info including os pid

select s.sid || ',' || s.serial# "SID/SERIAL"
, s.username
, s.osuser
, p.spid "OS PID"
, s.program
from v$session s
, v$process p
Where s.paddr = p.addr
order by to_number(p.spid);
--------------------------------------------------

Show a users current sql

Select sql_text
from v$sqlarea
where (address, hash_value) in
(select sql_address, sql_hash_value
from v$session
where username like '&username');

--------------------------------------------------

Session status associated with the specified os process id

select s.username
, s.sid
, s.serial#
, p.spid
, last_call_et
, status
from V$SESSION s
, V$PROCESS p
where s.PADDR = p.ADDR
and p.spid='&pid';

--------------------------------------------------

All active sql

set feedback off
set serveroutput on size 9999
column username format a20
column sql_text format a55 word_wrapped
begin
for x in
(select username||'('||sid||','||serial#||') ospid = '|| process ||
' program = ' || program username,
to_char(LOGON_TIME,' Day HH24:MI') logon_time,
to_char(sysdate,' Day HH24:MI') current_time,
sql_address,
sql_hash_value
from v$session
where status = 'ACTIVE'
and rawtohex(sql_address) <> '00'
and username is not null ) loop
for y in (select sql_text
from v$sqlarea
where address = x.sql_address ) loop
if ( y.sql_text not like '%listener.get_cmd%' and
y.sql_text not like '%RAWTOHEX(SQL_ADDRESS)%' ) then
dbms_output.put_line( '--------------------' );
dbms_output.put_line( x.username );
dbms_output.put_line( x.logon_time || ' ' || x.current_time || ' SQL#=' || x.sql_hash_value);
dbms_output.put_line( substr( y.sql_text, 1, 250 ) );
end if;
end loop;
end loop;
end;


--------------------------------------------------

Display any long operations

select username
, to_char(start_time, 'hh24:mi:ss dd/mm/yy') started
, time_remaining remaining
, message
from v$session_longops
where time_remaining = 0
order by time_remaining desc;

--------------------------------------------------

List open cursors per user

select sess.username
, sess.sid
, sess.serial#
, stat.value cursors
from v$sesstat stat
, v$statname sn
, v$session sess
where sess.username is not null
and sess.sid = stat.sid
and stat.statistic# = sn.statistic#
and sn.name = 'opened cursors current'
order by value;

Or alternatively...

select count(hash_value) cursors
, sid
, user_name
from v$open_cursor
group by
sid
, user_name
order by
cursors;



source: http://www.shutdownabort.com/dbaqueries/Administration_Session.php#List-open-cursors-per-user

2010年12月22日 星期三

Google Map 的中文製作簡介V2

source; http://dev.leck.com/gsupport/

DWR和jQuery的$符號衝突的問題

DWR和jQuery都使用$符號來取得html中的元素,如果兩個一起用的話就會出現衝突。我在網上搜了一下都是用jQuery.noConflict()來替換jQuery中的$,但jQuery的插件太多,我不想在每個dwr調用之前都用noConflict來轉換一下,能不能把dwr的$符號替換一下呢?查了一下dwr的util.js源碼,發現下面這句:

var $;
if (!$) {
$ = dwr.util.byId;
}

沒錯!這裡就是dwr定義$的地方,把這斷代碼屏掉,重新定義了一下

var wwwju51com;
if (!wwwju51com) {
wwwju51com = dwr.util.byId;
}

OK,這樣你就能用wwwju51com來替換DWR中的$符號了。

2010年12月20日 星期一

Apache日誌分析簡介

apache日誌分析簡介
上次因工作的需求對一台apache的log做了一次整體的分析,所以順便也對apache的日誌分析做下簡單的介紹,主要參考apache官網的Log Files,手冊參照http://httpd.apache .org/docs/2.2/logs.html

一.日誌分析
如果apache的安裝時採用默認的配置,那麼在/logs目錄下就會生成兩個文件,分別是access_log和error_log
1.access_log
access_log為訪問日誌,記錄所有對apache服務器進行請求的訪問,它的位置和內容由CustomLog指令控制,LogFormat指令可以用來簡化該日誌的內容和格式
例如,我的其中一台服務器配置如下

CustomLog "| /usr/sbin/rotatelogs /var/log/apache2/%Y_%m_%d_other_vhosts_access.log 86400 480" vhost_combined

-rw-r--r-- 1 root root 22310750 12-05 23:59 2010_12_05_other_vhosts_access.log
-rw-r--r-- 1 root root 26873180 12-06 23:59 2010_12_06_other_vhosts_access.log
-rw-r--r-- 1 root root 26810003 12-07 23:59 2010_12_07_other_vhosts_access.log
-rw-r--r-- 1 root root 24530219 12-08 23:59 2010_12_08_other_vhosts_access.log
-rw-r--r-- 1 root root 24536681 12-09 23:59 2010_12_09_other_vhosts_access.log
-rw-r--r-- 1 root root 14003409 12-10 14:57 2010_12_10_other_vhosts_access.log


通過CustomLog指令,每天一天生成一個獨立的日誌文件,同時也寫了定時器將一周前的日誌文件全部清除,這樣可以顯得更清晰,既可以分離每一天的日誌又可以清除一定時間以前的日誌通過制,LogFormat定義日誌的記錄格式

LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
LogFormat "%{X-Forwarded-For}i %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combinedproxy
LogFormat "%h %l %u %t \"%r\" %>s %b" common
LogFormat "%{Referer}i -> %U" referer
LogFormat "%{User-agent}i" agent

隨意的tail一個access_log文件,下面是一條經典的訪問記錄

218.19.140.242 - - [10/Dec/2010:09:31:17 +0800] "GET /query/trendxml/district/todayreturn/month/2009-12-14/2010-12-09/haizhu_tianhe.xml HTTP/ 1.1" 200 1933 "-" "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 (.NET CLR 3.5.30729)"

一共是有9項,將他們一一拆開

218.19.140.242
-
-
[10/Dec/2010:09:31:17 +0800]
"GET /query/trendxml/district/todayreturn/month/2009-12-14/2010-12-09/haizhu_tianhe.xml HTTP/1.1"
200
1933
"-"
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 (.NET CLR 3.5.30729)"

1) 218.19.140.242 這是一個請求到apache服務器的客戶端ip,默認的情況下,第一項信息只是遠程主機的ip地址,但我們如果需要apache查出主機的名字,可以將HostnameLookups設置為on ,但這種做法是不推薦使用,因為它大大的減緩了服務器.另外這裡的ip地址不一定就是客戶主機的ip地址,如果客戶端使用了代理服務器,那麼這裡的ip就是代理服務器的地址,而不是原機.

2) - 這一項是空白,使用"-"來代替,這個位置是用於標註訪問者的標示,這個信息是由identd的客戶端存在,除非IdentityCheck為on,非則apache是不會去獲取該部分的信息(ps:不太理解,基本上這一項都是為空,奉上原文)
The "hyphen" in the output indicates that the requested piece of information is not available. In this case, the information that is not available is the RFC 1413 identity of the client determined by identd on the clients machine. This information is highly unreliable and should almost never be used except on tightly controlled internal networks. Apache httpd will not even attempt to determine this information unless IdentityCheck is set to On.

3) - 這一項又是為空白,不過這項是用戶記錄用戶HTTP的身份驗證,如果某些網站要求用戶進行身份雁陣,那麼這一項就是記錄用戶的身份信息

4) [10/Dec/2010:09:31:17 +0800] 第四項是記錄請求的時間,格式為[day/month/year:hour:minute:second zone],最後的+0800表示服務器所處的時區為東八區

5) "GET /..haizhu_tianhe.xml HTTP/1.1" 這一項整個記錄中最有用的信息,首先,它告訴我們的服務器收到的是一個GET請求,其次,是客戶端請求的資源路徑,第三,客戶端使用的協議時HTTP/1.1,整個格式為"%m %U%q %H",即"請求方法/訪問路徑/協議"

6) 200 這是一個狀態碼,由服務器端發送回客戶端,它告訴我們客戶端的請求是否成功,或者是重定向,或者是碰到了什麼樣的錯誤,這項值為200,表示服務器已經成功的響應了客戶端的請求,一般來說,這項值以2開頭的表示請求成功,以3開頭的表示重定向,以4開頭的標示客戶端存在某些的錯誤,以5開頭的標示服務器端存在某些錯誤,詳細的可以參見HTTP specification (RFC2616 section 10).[http://www.w3.org/Protocols/rfc2616/rfc2616.txt]

7) 1933 這項表示服務器向客戶端發送了多少的字節,在日誌分析統計的時侯,把這些字節加起來就可以得知服務器在某點時間內總的發送數據量是多少

8) - 暫不知

9) "Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9.2.8) Gecko/20100722 Firefox/3.6.8 (.NET CLR 3.5.30729)" 這項主要記錄客戶端的瀏覽器信息





2.error_log
error_log為錯誤日誌,記錄下任何錯誤的處理請求,它的位置和內容由ErrorLog指令控制,通常服務器出現什麼錯誤,首先對它進行查閱,是一個最重要的日誌文件

tail error_log,隨意摘取一個記錄

[Fri Dec 10 15:03:59 2010] [error] [client 218.19.140.242] File does not exist: /home/htmlfile/tradedata/favicon.ico

同樣也是分為幾個項

[Fri Dec 10 15:03:59 2010]
[error]
[client 218.19.140.242]
File does not exist: /home/htmlfile/tradedata/favicon.ico

1) [Fri Dec 10 15:03:59 2010] 記錄錯誤發生的時間,注意,它跟我們上面access_log記錄的時間格式是不同的

2) [error] 這一項為錯誤的級別,根據LogLevel指令來控制錯誤的類別,上面的404是屬於error級別

3) [client 218.19.140.242] 記錄客戶端的ip地址

4) File does not exist: /home/htmlfile/tradedata/favicon.ico 這一項首先對錯誤進行了描述,例如客戶端訪問一個不存在或路徑錯誤的文件,就會給出404的提示錯誤



二.實用的日誌分析腳本
了解日誌的各種定義後,這里分享一下從網上淘來的一些對日誌分析的腳本

1.查看apache的進程數
ps -aux | grep httpd | wc -l

2.分析日誌查看當天的ip連接數
cat default-access_log | grep "10/Dec/2010" | awk '{print $2}' | sort | uniq -c | sort -nr

3.查看指定的ip在當天究竟訪問了什麼url
cat default-access_log | grep "10/Dec/2010" | grep "218.19.140.242" | awk '{print $7}' | sort | uniq -c | sort -nr

4.查看當天訪問排行前10的url
cat default-access_log | grep "10/Dec/2010" | awk '{print $7}' | sort | uniq -c | sort -nr | head -n 10

5.看到指定的ip究竟乾了什麼
cat default-access_log | grep 218.19.140.242 | awk '{print $1"\t"$8}' | sort | uniq -c | sort -nr | less

6.查看訪問次數最多的幾個分鐘(找到熱點)
awk '{print $4}' default-access_log |cut -c 14-18|sort|uniq -c|sort -nr|head



三.使用awstats自動分析日誌
當然啦,如果想最簡單和最直觀的分析日誌還是用工具,現在網上較流行的工具是awstats,一個基於perl的web日誌分析工具,功能很強大也支持IIS等服務器
下載地址 http://awstats.sourceforge.net
安裝配置見 <安裝配置整理之 awstats>

2010年12月14日 星期二

Killing Oracle Sessions

detail http://www.oracle-base.com/articles/misc/KillingOracleSessions.php

Killing sessions can be very destructive if you kill the wrong session, so be very careful when identifying the session to be killed. If you kill a session belonging to a background process you will cause an instance crash.

Identify the offending session using the V$SESSION or GV$SESSION view as follows.
Identify the offending session using the V$SESSION or GV$SESSION view as follows.
SET LINESIZE 100 COLUMN spid FORMAT A10 COLUMN username FORMAT A10 COLUMN program FORMAT A45  SELECT s.inst_id,        s.sid,        s.serial#,        p.spid,        s.username,        s.program FROM   gv$session s        JOIN gv$process p ON p.addr = s.paddr AND p.inst_id = s.inst_id WHERE  s.type != 'BACKGROUND';     INST_ID        SID    SERIAL# SPID       USERNAME   PROGRAM ---------- ---------- ---------- ---------- ---------- ---------------------------------------------          1         30         15 3859       TEST       sqlplus@oel5-11gr2.localdomain (TNS V1-V3)          1         23        287 3834       SYS        sqlplus@oel5-11gr2.localdomain (TNS V1-V3)          1         40        387 4663                  oracle@oel5-11gr2.localdomain (J000)          1         38        125 4665                  oracle@oel5-11gr2.localdomain (J001)  SQL>
The SID and SERIAL# values of the relevant session can then be substituted into the commands in the following sections.

ALTER SYSTEM KILL SESSION

The basic syntax for killing a session is shown below.
SQL> ALTER SYSTEM KILL SESSION 'sid,serial#';

2010年12月7日 星期二

Oracle (JOB) 更新next_date的探討

Source: http://blog.csdn.net/kamus/archive/2004/12/02/201377.aspx


:本文通过实验和事件跟踪来分析Oracle Job执行过程中修改下次执行时间的机制。

有些人问,OracleJOB在设定完next_dateinterval之后,到底是什么时候决定下一次运行时间的。可以归纳成以下几个问题。

1. 假设我们的JOB设定第一次运行的时间是12:00,运行的间隔是1小时,JOB运行需要耗时30分钟,那么第二次运行是在13:00还是13:30

2. 如果是在13:00那是不是说明只要JOB一开始运行,next_date就被重新计算了?

3. JOB的下一次运行会受到上一次运行时间的影响吗?如果受到影响,如何可以避免这个影响而让JOB在每天的指定时刻运行?

4. 假设我们的JOB设定第一次运行的时间是12:00,运行的间隔是30分钟,JOB运行需要耗时1小时,那么第二次运行是在12:30还是13:00还是根本就会报错?

本文通过一些实验和跟踪来解释上面的所有问题。

首先我们选择一个测试用户,假设该用户名为kamus

由于我们在实验用的存储过程中会用到dbms_lock包,所以需要由sys用户先授予kamus用户使用dbms_lock包的权限。

d:\Temp>sqlplus "/ as sysdba"

SQL*Plus: Release 9.2.0.5.0 - Production on 星期三 12 1 23:56:32 2004

Copyright (c) 1982, 2002, Oracle Corporation. All rights reserved.

连接到:

Oracle9i Enterprise Edition Release 9.2.0.5.0 - Production

With the Partitioning, OLAP and Oracle Data Mining options

JServer Release 9.2.0.5.0 - Production

SQL> grant execute on dbms_lock to kamus;

授权成功。

然后用kamus用户登录数据库,创建我们测试使用的存储过程sp_test_next_date

create or replace procedure sp_test_next_date as
p_jobno number;
P_nextdate date;
begin
--将调用此存储过程的jobnext_date设置为30分钟以后

select job into p_jobno from user_jobs where what =
'sp_test_next_date;';
execute immediate
'begin dbms_job.next_date(' || to_char(p_jobno) || ',sysdate+1/48);commit;end;'
;
--修改完毕以后检查user_jobs视图,输出job目前的next_date

select next_date
into P_nextdate
from user_jobs
where what =
'sp_test_next_date;';
dbms_output.put_line(
'JOB执行中的next_date: '
||
to_char(p_nextdate,
'YYYY-MM-DD HH24:MI:SS'
));
--等待10秒再退出执行

dbms_lock.sleep(seconds =>
10);
end sp_test_next_date;

创建调用该存储过程的JOB,定义interval为每天一次,也就是这次执行以后,下次执行时间应该在1天以后。

SQL> variable jobno number;

SQL> BEGIN

2 DBMS_JOB.SUBMIT(job => :jobno,

3 what => 'sp_test_next_date;',

4 next_date => SYSDATE,

5 interval => 'SYSDATE+1');

6 COMMIT;

7 END;

8 /

PL/SQL 过程已成功完成。

jobno

---------

1

然后我们手工执行存储过程,执行完毕以后再手工从user_jobs视图中获得JOB的下次执行时间,可以看到在存储过程中修改的JOB的下次执行时间已经生效,变成了当前时间的30分钟以后,而不是默认的1天以后。

SQL> conn kamus

请输入口令:

已连接。

SQL> set serverout on

SQL> exec sp_test_next_date();

JOB执行中的next_date: 2004-12-02 00:44:11

PL/SQL 过程已成功完成。

SQL> col next_date for a20

SQL> select to_char(next_date,'YYYY-MM-DD HH24:MI:SS') next_date from user_jobs

where what = 'sp_test_next_date;';

NEXT_DATE

--------------------

2004-12-02 00:44:11

我们再手工运行JOB,看看这次的结果,可以发现JOB没有运行完毕以前被修改了的下次运行时间跟JOB运行完毕以后再次手工检索user_jobs视图获得的下次运行时间已经不相同了。由此我们可以得出一个结论,next_date是在JOB运行完毕以后被Oracle自动修改的,而不是在JOB刚开始运行的时候,因为我们在存储过程中修改的next_dateJOB运行结束之后又被修改为默认的1天以后了。

SQL> exec dbms_job.run(1);

JOB执行中的next_date: 2004-12-02 00:54:52

PL/SQL 过程已成功完成。

SQL> select to_char(next_date,'YYYY-MM-DD HH24:MI:SS') next_date from user_jobs

where what = 'sp_test_next_date;';

NEXT_DATE

--------------------

2004-12-03 00:24:52

现在我们再次修改存储过程,输出存储过程开始执行的时间,便于跟执行完毕以后的JOB下次执行时间进行比较。

create or replace procedure sp_test_next_date as
p_jobno number;
P_nextdate date;
begin
--输出JOB刚开始执行的时间

dbms_output.put_line(
' JOB开始执行的时间: ' ||
to_char(sysdate,
'YYYY-MM-DD HH24:MI:SS'
));
--将调用此存储过程的jobnext_date设置为30分钟以后

select job into p_jobno from user_jobs where what =
'sp_test_next_date;';
execute immediate
'begin dbms_job.next_date(' || to_char(p_jobno) || ',sysdate+1/48);commit;end;'
;
--修改完毕以后检查user_jobs视图,输出job目前的next_date

select next_date
into P_nextdate
from user_jobs
where what =
'sp_test_next_date;';
dbms_output.put_line(
' JOB执行中的next_date: '
||
to_char(p_nextdate,
'YYYY-MM-DD HH24:MI:SS'
));
--等待10秒再退出执行

dbms_lock.sleep(seconds =>
10);
end sp_test_next_date;

重新进行测试,我们可以发现JOBnext_dateJOB开始执行时间的1天以后,而不是JOB结束时间的1天以后(因为JOB结束需要经过10秒钟)

SQL> exec dbms_job.run(1);

JOB开始执行的时间: 2004-12-02 00:38:24

JOB执行中的next_date: 2004-12-02 01:08:24

PL/SQL 过程已成功完成。

SQL> select to_char(next_date,'YYYY-MM-DD HH24:MI:SS') next_date from user_jobs

where what = 'sp_test_next_date;';

NEXT_DATE

--------------------

2004-12-03 00:38:24

至此,我们已经说明了两个问题。就是:JOB在运行结束之后才会更新next_date,但是计算的方法是JOB刚开始的时间加上interval设定的间隔。

下面我们通过trace来再次求证这个结论。

SQL> ALTER SESSION SET EVENTS '10046 trace name context forever, level 12';

会话已更改。

SQL> exec dbms_job.run(1);

PL/SQL 过程已成功完成。

SQL> ALTER SESSION SET EVENTS '10046 trace name context off';

会话已更改。

执行完毕以后在udump目录中查看生成的trace文件。如果我们用tkprof来格式化这个trace文件然后再查看格式化后的结果,我们会感到很诧异。因为在格式化完毕的SQL执行顺序中,更新job$表的语句出现在dbms_job.next_date语句之前,也就是看上去是Oracle先按照interval自动更新了JOBnext_date,然后才继续往下执行存储过程中定义的next_date更新语句,而这样显然无法解释我们在上面的实验中看到的结果。

但是当我们跳过tkprof而直接去查看生成的trace文件,就会恍然大悟,同时也印证了steve adamsixora上提到的观点:tkprof格式化完的结果会省略一些信息,甚至在有时候会给我们错误的信息。

直接查看trace文件,我们可以看到如下的执行顺序:

1. parse cursor #10oracle根据interval和先前保存的this_date字段值更新job$表的语句,包括更新failures, last_date, next_date, total等)

2. parse cursor #15(存储过程中的begin dbms_job.next_date语句)

3. binds cursor #15(将加上了30分钟的时间绑定到cursor #15上)

4. exec cursor #15(执行cursor #15

5. wait cursor #11(经历一个PL/SQL lock timer事件,也就是存储过程中执行的dbms_lock.sleep方法)

6. binds cursor #10(将JOB刚开始执行时候的时间绑定到cursor #10上)

7. exec cursor #10(执行cursor #10

也就是说虽然更新job$的语句被很早地解析过了,但是直到JOB运行结束时这个被解析过的游标才开始作变量绑定进而开始执行。

正是因为解析update sys.job$语句的时间早于解析begin dbms_job.next_date语句的时间,所以tkprof的结果将前者放在了前面。

接下来我们进入另外一个问题的探讨,本文最开始提到的第四个问题:

假设我们的JOB设定第一次运行的时间是12:00,运行的间隔是30分钟,JOB运行需要耗时1小时,那么第二次运行是在12:30还是13:00还是根本就会报错?

通过分析trace文件我们可以找到更新next_dateSQL语句是:

update sys.job$ set failures=0, this_date=null, flag=:1, last_date=:2, next_date=greatest(:3,sysdate),total=total+(sysdate - nvl(this_date, sysdate)) where job=:4

注意到更新next_date字段的公式是greatest(:3, sysdate),此处的:3绑定的是jobthis_date+interval。所以我们猜测实际上应该是有一个跟当前时间的比较机制,如果在执行完JOB之后的时间比按照this_date+interval计算出的时间更晚一些,那么next_date就更新为当前时间,也就是几乎会立刻再重新执行JOB

同样这样的猜测我们也需要通过实验来验证一下。

创建一个新的存储过程sp_test_next_date1,简单地等待2分钟,但是我们将调用这个存储过程的JOBinterval设置为1分钟,看看会有什么情况。

为了更方便得比较,我们创建一个表用来记录每次JOB执行的开始时间。

SQL> create table t (cdate date);

Table created

创建存储过程的脚本

create or replace procedure sp_test_next_date1 as
begin
--输出JOB开始执行的时间
insert into t(cdate) values(sysdate);
commit
;
--等待120秒退出

dbms_lock.sleep(seconds =>
120);
end sp_test_next_date1;

创建调用此存储过程的JOB

SQL> variable jobno number;

SQL> BEGIN

2 DBMS_JOB.SUBMIT(job => :jobno,

3 what => 'sp_test_next_date1;',

4 next_date => SYSDATE,

5 interval => 'SYSDATE+1/1440');

6 COMMIT;

7 END;

8 /

PL/SQL 过程已成功完成。

jobno

---------

7

执行此JOB,然后过一段时间开始检查表t中的输出。

SQL> select * from t order by cdate;

CDATE

--------------------

2004-12-3 14:10:43

2004-12-3 14:12:47

2004-12-3 14:14:55

2004-12-3 14:16:59

2004-12-3 14:19:07

2004-12-3 14:21:11

6 rows selected

首先我们确认JOB每次都是成功执行了,并没有任何报错,然后检查cdate字段,发现时间间隔都是2分钟左右,也就是说因为JOB本身的interval设定比JOB本身的执行时间要长,所以Oraclenext_date设置为每次JOB结束的时间。

同时我们也注意到,每次开始的时间都有4秒到8秒的延迟,没有继续深究,不确认这是因为oracle本身计算的误差,还是内部比如启动Job Process需要的时长。

不论如何,到此我们也已经回答了第四个问题,即使interval的时长短于JOB执行的时间,整个作业仍然会继续进行,只是执行间隔变为了JOB真实运行的时长。

由于trace文件过长,所以不在本文中贴出了,如果有兴趣可以发邮件给我。我的邮件地址是:kamus@itpub.net

本文的最后一部分,解答本文开头提出的第三个问题,也就是:

JOB的下一次运行会受到上一次运行时间的影响吗?如果受到影响,如何可以避免这个影响而让JOB在每天的指定时刻运行?

JOB的下一次运行时间是会受上一次影响的,如果我们的interval仅仅是sysdate+1/24这样的形式的话,无疑,上次执行的时间再加上1小时就是下次执行的时间。那么如果JOB因为某些原因延迟执行了一次,这样就会导致下一次的执行时间也同样顺延了,这通常不是我们希望出现的现象。

解决方法很简单,只需要设定正确的interval就可以了。

比如,我们要JOB在每天的凌晨3:30执行而不管上次执行到底是几点,只需要设置intervaltrunc(SYSDATE)+3.5/24+1即可。完整的SQL如下:

SQL> variable jobno number;

SQL> BEGIN

2 DBMS_JOB.SUBMIT(job => :jobno,

3 what => 'sp_test_next_date;',

4 next_date => SYSDATE,

5 interval => 'trunc(SYSDATE)+3.5/24+1');

6 COMMIT;

7 END;

8 /

BTW:在trace文件中发现虽然通过select rowid from table返回的结果已经是扩展ROWID格式(Data Object number + File + Block + ROW)了,但是oracle内部检索数据仍然在使用限制ROWID格式(Block number.Row number.File number)。

本文涉及到的额外知识可以参看我的其它技术文章:

1. 通过事件跟踪SQL执行的后台步骤

2. Oracle等待事件,比如本文提到的PL/SQL lock timer

3. ROWID格式

作者简介:

张乐奕,网名kamus

曾任ITPUB Oracle认证版版主,现任itpub Oracle管理版版主.

现任职于北京某大型软件公司,首席DBA,主要负责证券行业的全国十数处核心交易系统数据库管理及维护工作。

热切关注Oracle技术和其它相关技术,出没于各大数据库技术论坛,目前是中国最大的Oracle技术论坛www.itpub.net的数据库管理版版主,

阅读更多技术文章和随笔可以登录我的个人blog
http://blog.cdsn.net/kamus