日付関数に関する検証 外伝 その2

<日付関数に関する検証 外伝>
ペンネーム: ダーリン

【SYSTIMESTAMPとCURRENT_TIMESTAMPの怪 その2】

前回は複数の日付取得関数を1SQLの中で実行すると、関数によっては取得した
時刻がずれる場合があるということをお話しました。

その原因を探るべくトレースを取得し、ひとまず、日付取得関数が発行してい
ると思われるシステムコール(gettimeofday)まで特定することができたわけで
すが、、、、

さて今週は、 1SQL で複数の日付取得関数を記述した場合のトレースを確認し
てみましょう。

気になる SYSTIMESTAMP 関数と CURRENT_TIMESTAMP 関数を同時に取得するこ
とにします。すると、、、

SQL> select SYSTIMESTAMP,CURRENT_TIMESTAMP from dual;

SYSTIMESTAMP                    CURRENT_TIMESTAMP
------------------------------- --------------------------------
06-11-20 22:26:47.643464 +09:00 06-11-20 22:26:47.643513 +09:00
gettimeofday({1164029207, 643368}, NULL) = 0
gettimeofday({1164029207, 643464}, NULL) = 0   <<<<
gettimeofday({1164029207, 643513}, NULL) = 0   <<<<
gettimeofday({1164029207, 643558}, NULL) = 0

出てきました。
秒未満の時刻を検索すると SYSTIMESTAMP 関数の値と、CURRENT_TIMESTAMP
関数の値に合致するトレースが出てきました。

しかも、それぞれの日付取得関数ごとにシステム関数を呼び出していることが
わかります。つまり、これが根本的な原因となり 1SQL 内であってもミリ秒未
満のタイムラグが発生していると考えられます。

同様に他の関数についても複数記述してトレースを取りましたが、どうやら以
下のルールがあるようです。

gettimeofday 関数が発行される単位
第 1 グループ
SYSDATE

第 2 グループ
SYSTIMESTAMP

第 3 グループ
CURRENT_DATE
CURRENT_TIMSTAMP
LOCALTIMESTAMP

つまり CURRENT_DATE と CURRENT_TIMESTAMP および LOCALTIMSTAMP 関数を複
数回呼び出した場合は gettimeofday 関数が 1 度だけ呼び出されます。
言い換えれば、これらの関数の間では差は出ません。
SYSDATE 関数と、 SYSTIMESTAMP 関数はそれぞれ個別に gettimeofday 関数が
呼び出されます。これらはミリ秒未満の時差を持っていると思って間違いあり
ません。ちなみに同じ関数を複数回実行しても gettimeofday 関数は複数回実
行されることはありませんでした。同じ関数であれば、タイムラグは出ないと
いうことです。

ちょっと意地悪してみます。
“ミリ秒”単位でずれるということは、タイミングが悪いと、見た目上において
“分”レベルでもずれるかも。

そこでスクリプト(下記参照)を実行してみました。
と、意外と簡単に”分”レベルでずれる現象を確認できてしまいました。

     SYSTIMESTAMPの結果        CURRENT_TIMESTAMPの結果
1: [ 20061120230338.999999 ] [ 20061120230339.000002 ]
2: [ 20061120230356.999998 ] [ 20061120230357.000001 ]
3: [ 20061120230359.999999 ] [ 20061120230400.000002 ] <<<<
             ~~~~~~                    ~~~~~~
4: [ 20061120230406.999999 ] [ 20061120230407.000002 ]

SYSTIMESTAMP 関数では、23時03分ですが、CURRENT_TIMESTAMP 関数では23時
04分になってしまいました。
ということはもっとタイミングが悪いと”時”もずれる可能性はある!!

もうひとつ。
TIMESTAMP 型のフォーマットでは秒未満の表示を 9 桁まで定義することがで
きます。たとえば、テーブルの列定義では TIMESTAMP ( 9 ) などと記述しま
す。しかし、9 桁まで 0 以外の数字が埋まった環境を見たことがありません。
上記の結果を踏まえると、 これはひとえに gettimeofday 関数の返却値がマ
イクロ秒 ( 10 の -6 乗 ) 単位であることに起因していると考えられます。
ということは、少なくとも現時点では、Oracle の関数で日付時刻を取得する
場合、TIMESTAMP 型を 9 桁で定義することにあまりメリットはないことにな
りますね。
# アプリケーションのデータを格納する場合はまた別の話なので、十分に
# 仕様検討して使いこなしてください。

さて、関数によってミリ秒未満のタイムラグが発生する原因は、 「データソ
ースは同じ ( OS の時刻 ) だが、取得タイミングが違うため。」ということ
になりました。

さてさて、いかがでしたでしょうか。

皆さんのミリ秒未満の気持ち悪さをいくばくか解消できたならば、無用の知
識も捨てたものではありません。

あーすっきり!!

次週は待ちイベント関連を見てみたいと思います。

SYSTIMESTAMP 関数とCURRENT_TIMESTAMP 関数を使った場合の秒レベルの差異
が発生したケースをピックアップするスクリプト

# 実行前に "set serveroutput on" を忘れずに。
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
declare
 v_dummy_systm_time char(21) := null;
 v_dummy_ct_time char(21)    := null;
 v_dummy_systm_ss char(2) := null;
 v_dummy_ct_ss char(2)    := null;
begin
for i in 1..1000000 loop
select to_char(SYSTIMESTAMP,'ss')
     , to_char(CURRENT_TIMESTAMP,'ss')
     , to_char(SYSTIMESTAMP,'yyyymmddhh24miss.ff')
     , to_char(CURRENT_TIMESTAMP,'yyyymmddhh24miss.ff')
  into v_dummy_systm_ss,v_dummy_ct_ss
     , v_dummy_systm_time,v_dummy_ct_time
  from dual;
if v_dummy_systm_ss <> v_dummy_ct_ss then
  dbms_output.put_line('systm_time =[ ' || v_dummy_systm_time ||' ] ct_time=[ '|| v_dummy_ct_time ||' ]');
end if;
end loop;
end;
/
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

F1が終わると急に年の瀬が近くなってくる茅ヶ崎にて