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

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

【SYSTIMESTAMPとCURRENT_TIMESTAMPの怪 その1】

大変ご無沙汰しております。久々登場のダーリンです。
今回は日付関数のちょっと変わった動きについてお話させていただきます。

Oracle 9i 以降、秒未満の時刻を表示できる日付時刻に関する関数が増えてい
ることはすでにご存知かと思います。
従来から存在する SYSDATE 関数とあわせるとこれらは以下の 5つの関数です。

     SYSDATE                DATE型
     SYSTIMESTAMP           TIMESTAMP WITH TIMEZONE型
     CURRENT_DATE           DATE型
     CURRENT_TIMESTAMP      TIMESTAMP WITH TIMEZONE型
     LOCAL_TIMESTAMP        TIMESTAMP型

すでに気がついている方もいるかと思いますが、 実は上記の関数のなかであ
る組み合わせの関数を実行すると、、、ちがう値を取得してきます。
タイムゾーンの設定による違いではありません。
タイムゾーンについてはこちら
(http://www.atmarkit.co.jp/fdb/rensai/sqlclinic04/sqlclinic04_3.html)を
参照してみてください。

ここでいう異なる値とは、ミリ秒(1/1000秒)以下のずれのことです。

以下はその実例です。

SQL> select SYSDATE,SYSTIMESTAMP,CURRENT_TIMESTAMP,LOCALTIMETAMP from dual;

SYSDATE  SYSTIMESTAMP                    CURRENT_TIMESTAMP               
-------- ------------------------------- ------------------------------- 
06-11-20 06-11-20 21:40:40.978854 +09:00 06-11-20 21:40:40.978898 +09:00 

LOCALTIMESTAMP
------------------------
06-11-20 21:40:40.978898

SYSTIMESTAMP と、 CURRENT_TIMESTAMP および LOCALTIMESTAMP の時刻が数十
マイクロ秒のレベルでずれていますね。

多くの方は上記の SQL では暗黙的に同じ値が取得されることを期待している
のではないでしょうか?

日付関数を並べるなんてナンセンスと思われるかもしれませんが、これらの日
付関数では取得される TIMEZONE が異なるため、TIMEZONE を意識する場合は
便利な使い方です。

これらの関数について、マニュアルにはおおよそ以下のように記載されていま
す。

SYSDATE            このファンクションはローカル・データベースの
                   日時を戻します。
SYSTIMESTAMP       データベースが存在するシステムの タイムゾーン
                   (小数部を含む)を戻します。
CURRENT_DATE       セッション・タイムゾーンの現在の日付を DATE 
                   データ型のグレゴリオ暦の値で戻します。
CURRENT_TIMESTAMP  セッション・タイムゾーンの現在の日付および
                   時刻をTIMESTAMP WITH TIME ZONE データ型の値
                   で戻します。
LOCALTIMESTAMP     セッションのタイムゾーンの現在の日付および
                   時刻をTIMESTAMPデータ型の値で戻します。

つまり SYSDATE 関数と SYSTIMESTAMP 関数はローカルデータベース、あるい
は、システムの日時データを戻しているのに対し、それ以外の関数はセッショ
ンタイムゾーンを考慮した日時を戻しているようです。
日時データのデータソースが異なるか、あるいは、同一データに対して変換を
かけて値を取得しているのでしょうか。

Oracle がそもそもどうやって日付時刻の情報を取得しているのか気になります。

こんなときどうすればこのような情報を得ることができるでしょうか?
何か尻尾ぐらいはつかめないでしょうか。 というわけで、今回はトレースを
とることにします。

まず、psコマンドでトレースを取得するプロセスを見つけます。
トレースする対象のセッションは専用接続で接続し、 そのプロセスを特定する
ほうがわかりやすくておすすめです。

今回はLinux上でトレースを取得するので、strace コマンドを使ってみます。

□■ 対象セッションのプロセス特定
$ ps -elf | grep oracle | grep test1 | grep beq
000 S oracle   15690     1  1  77   0    - 60699 pipe_w 21:40 ?   00:00:00 oracletest1 (DESCRIPTION=(LOCAL=YES)(ADDRESS=(PROTOCOL=beq)))


□■ 対象セッションのトレース
$ strace -p 15690
Process 15690 attached - interrupt to quit
read(8,

strace コマンドの 第2引数はトレースするPIDです。
これで準備完了です。では対象のセッションで日付を取得してみましょう。

まずはSYSDATEです。

SQL> select SYSDATE from dual;

SYSDATE
--------
06-11-20

おーっと トレースも出ました。・・・が何のことやら。

"\0252\0\06\0\0\0\0\03^25a200\0\0\0\0\0\0204332f"..., 2064) = 170
gettimeofday({1164026662, 615562}, NULL) = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 20000}, ru_stime={0, 30000}, ...}) = 0
times(NULL)                             = 851498339
gettimeofday({1164026662, 615733}, NULL) = 0
getrusage(RUSAGE_SELF, {ru_utime={0, 20000}, ru_stime={0, 30000}, ...}) = 0
gettimeofday({1164026662, 615835}, NULL) = 0
gettimeofday({1164026662, 615867}, NULL) = 0
times(NULL)                             = 851498339
getrusage(RUSAGE_SELF, {ru_utime={0, 20000}, ru_stime={0, 30000}, ...}) = 0
gettimeofday({1164026662, 615979}, NULL) = 0
times(NULL)                             = 851498339
times(NULL)                             = 851498339
times(NULL)                             = 851498339
getrusage(RUSAGE_SELF, {ru_utime={0, 20000}, ru_stime={0, 30000}, ...}) = 0
gettimeofday({1164026662, 616426}, NULL) = 0
gettimeofday({1164026662, 616704}, NULL) = 0
....................
....................

だらだらーーー。

でも、どうやら時刻取得に関連する関数 (gettimeofday) が実行されている形
跡があります。

はたして、この中に、SYSDATE関数に関連する情報はあるのでしょうか?

これだけを眺めていてもちっともらちがあかないので、 とりあえず他の関数
も実行して違いを見ることにしましょう。

SQL> select SYSDATE from dual;

SYSDATE
--------
06-11-20

SQL> select SYSTIMESTAMP from dual;

SYSTIMESTAMP
---------------------------------------------------------------------------
06-11-20 21:45:28.630072 +09:00


SQL> select CURRENT_DATE from dual;

CURRENT_
--------
06-11-20


SQL> select CURRENT_TIMESTAMP from dual;

CURRENT_TIMESTAMP
---------------------------------------------------------------------------
06-11-20 21:46:54.970684 +09:00


SQL> select LOCALTIMESTAMP from dual;

LOCALTIMESTAMP
---------------------------------------------------------------------------
06-11-20 21:47:34.789149

さてっと、、、、、

この手のログはどれもそうですが一見して何のことかわかりません。
とにかく、SYSTIMESTAMPとLOCALTIMESTAMPのトレースを比べてみます。

トレースの量自体が違いますが、よく見ると、それぞれトレースの後半部分に
WRITE や READ のシステムコールがあったりするなどよく似ています。

さらにじっと見ていると、、、、、
SYSTIMESTAMP のトレースの 40 行目近辺にヒントがありました。
# SQLの実行状況によって(初めて実行したか、2回目以降かなど)トレースの
# 状況が変わるので行数は参考程度に考えてください。

ここで発行された gettimeofday 関数の返却値に着目します。すると、、

SQL> select SYSTIMESTAMP from dual;

SYSTIMESTAMP
---------------------------------------------------------------------------
06-11-20 21:45:28.630072 +09:00
                  ~~~~~~

40  gettimeofday({1164026728, 629995}, NULL) = 0
41  gettimeofday({1164026728, 630072}, NULL) = 0   <<<< 
                              ~~~~~~
42  gettimeofday({1164026728, 630129}, NULL) = 0

んんんんー。gettimeofday関数の引数の真ん中にある数値が、SYSTIMESTAMP
関数の返却値と同じです。どうやら、SYSTIMESTAMP 関数が呼び出したシステ
ム関数はここで実行されている gettimeofday 関数のようです。

gettimeofday 関数の仕様は man ページによると、第 1 引数は時刻の取得、
第 2 引数はタイムゾーンの取得です。 また、現在この関数を使用して、
タイムゾーンを取得していない旨の記述があります。なるほど、トレース
の第 2 引数は常に “NULL” です。

ちなみに第 1 引数の内訳ですが、構造体の 1 番目の返却値は second
(秒)、2 番目の返却値は microseconds (マイクロ秒)です。

どうやら Oracle の日時のデータソースは、ここで実行されている
gettimeofday 関数だと思って間違いないでしょう。

# ところで、下記の SQL を実行すれば、先ほどの第 1 引数を日時に変換する
# ことができます。
#
# select to_char(to_date(‘1970/01/01 00:00:00′,’yyyy/mm/dd hh24:mi:ss’)
# + /24/60/60 + 9/24,’yyyy/mm/dd hh24:mi:ss’)
# from dual
#
# なお、”+ 9/24″ は JST への変換用です。
# もちろん、JST と GMT の時間差分(*) です。
#
# (*) 日本の時刻は 9 時間早い。おかげでボジョレーも 9 時間早くいただけます。

上記の値を当てはめると、、

  1  select to_char(to_date('1970/01/01 00:00:00','yyyy/mm/dd hh24:mi:ss')
  2         + 1164026728/24/60/60 + 9/24,'yyyy/mm/dd hh24:mi:ss') DATE_TIME
  3* from dual
SQL> /

DATE_TIME
-------------------
2006/11/20 21:45:28

ぴったんこ。

ちょっと長くなってきましたので、続きは来週にしましょう。

おおっ! 地デジが受信できた。地デジ最高!! 茅ヶ崎にて