最近 mod_perl Mailing List が出来たので、さっそく参加。
その絡み、というわけじゃないんだけどこの日記の補足というか、続きというか、 Apache::DBI + PostgreSQL + mod_perl 環境でのトランザクションの扱い (AutoCommit => 0 の場合)でハマった覚え書き。
PostgreSQL で現在時刻を求めるのに、関数 now() とか文字列 'now' を日付・時刻型のフィールドに入れると現在時刻になる、とか、その手の手法をよく使う。 カラムの default 値として now() を設定しておくと、 Insert 時に自動的に日付を入れられるので重宝してるし。 ただしこの「現在時刻」は、厳密には「トランザクション開始時刻」なので、一トランザクション内で何回実行しても (どれだけ実時間が経過していても) 同じ値が返る。
しかしここに落とし穴が。 mod_perl + Apache::DBI 環境だと DB 接続が httpd の子プロセス毎に永続化する、つまり httpd も DB のバックエンドプロセスも生き続ける関係で、
- 初回スクリプト実行時に commit (もしくは abort) した時点でトランザクション終了。 かつ、その時点から次のトランザクション開始。
- トランザクションに入ったまま、次のアクセスがあるまでプロセスは待機。
- 2回目のスクリプト実行時に now() を呼ぶと、トランザクション開始時刻 (つまり、初回のスクリプト終了時刻)が返る! 2回目のスクリプト開始時刻ではなく!!
という現象が起きるのであった。2回目のスクリプト実行時に「現在時刻」として欲しいのは、前回の終了時刻じゃなくて、今回の開始時刻な訳だ。 アクセスがめちゃくちゃ頻繁なら問題ないかもしれないけど、不幸にも(笑)アクセスが少なくて、 「時間が10分とか20分とかずれる」というクレームが来て発覚。
ちなみに PostgreSQL には timeofday() という関数もあって、これはトランザクション開始時刻ではなく真の現在時刻を返してくれるので、now() の代わりにこっちを使えば問題ない (ただし OS のシステムコール呼び出しを伴うので遅い)。 だが、「文字列 'now' を日付型カラムに突っ込む」という安直かつ応用の効く方法を既に色々な場所で使っている関係上、なんとか SQL をいじらずに回避出来ないものか・・・
結局、処理の冒頭 ( DBI->connect した直後 ) に
$dbh->commit;
を行うことで回避できる事を発見。(commit した時点でトランザクション終了、新トランザクション開始、になるから) なーんか気持ち悪いというか、不自然で嫌なんだけど。しょうがないか。
正攻法で行くならスクリプト開始時に timeofday() で現在時を得て変数に取っておいて、それを以後使いまわす、ということになるのかな。
- Apache 1.3.14
- Perl 5.005_03
- mod_perl 1.25
- Apache::DBI 0.88
- PostgreSQL 7.1.2