PostgreSQL

PostgreSQL 時刻を返す関数の一覧

PostgreSQL の CURRENT_TIMESTAMP は、トランザクションを始めた時刻を返します。COMMIT するまでは、何度呼んでも同じ値です。Oracle・MySQL・Db2 では「SQL文を実行した時刻」が返るので、PostgreSQL だけ動きが違います。トランザクションの中で処理時間を測ろうとしても、CURRENT_TIMESTAMP を使うと差が0になってしまいます。そういうときは clock_timestamp() を使います、、というメモです。

時刻を返す関数の一覧

関数返る時刻返る型
CURRENT_TIMESTAMPトランザクション開始時刻timestamp with time zone
now()
transaction_timestamp()
〃timestamp with time zone
CURRENT_TIME〃time with time zone
CURRENT_DATE〃date
LOCALTIMESTAMP〃timestamp without time zone
LOCALTIME〃time without time zone
statement_timestamp()SQL文の開始時刻timestamp with time zone
clock_timestamp()呼んだ瞬間の時刻(行ごとに変わる)timestamp with time zone
timeofday()呼んだ瞬間の時刻text(古い関数。計算に使うなら clock_timestamp() のほうがよい)

※ CURRENT_〜 と LOCAL〜 はカッコを付けずに書きます(current_timestamp() は構文エラー)。now() と CURRENT_TIMESTAMP の違いは「PostgreSQL now() と CURRENT_TIMESTAMP の違い(値は同じ、でも意味はある)」にまとめています。

試してみる

トランザクションの中で比べる
BEGIN;
SELECT now(), current_timestamp, current_time, localtime,
       statement_timestamp(), clock_timestamp();
SELECT pg_sleep(1.5);
SELECT now(), current_timestamp, current_time, localtime,
       statement_timestamp(), clock_timestamp();
COMMIT;

結果(PostgreSQL 15。見やすいように列を縦に並べています)

                      1回目                2回目(1.5秒後)
now()                 02:29:32.5652+09     02:29:32.5652+09     ← 同じ
current_timestamp     02:29:32.5652+09     02:29:32.5652+09     ← 同じ
current_time          02:29:32.5652+09     02:29:32.5652+09     ← 同じ
localtime             02:29:32.5652        02:29:32.5652        ← 同じ
statement_timestamp() 02:29:32.565876+09   02:29:34.069516+09   ← 進む
clock_timestamp()     02:29:32.566808+09   02:29:34.069595+09   ← 進む

2回目の SELECT では、now()・CURRENT_TIMESTAMP・CURRENT_TIME・LOCALTIME が1回目と同じ値になります。statement_timestamp() と clock_timestamp() は1.5秒ほど進みます。

1つのSQL文の中で比べる
SELECT g, statement_timestamp(), clock_timestamp()
FROM generate_series(1, 5) g;

statement_timestamp() は5行とも同じ値になり、clock_timestamp() は行ごとに少しずつ進みます。

ほかのRDBMSとの対応

PostgreSQLOracleMySQLDb2 for i
now()
(トランザクション開始時刻)
なしなしなし
statement_timestamp()SYSTIMESTAMP
SYSDATE
NOW()CURRENT TIMESTAMP
clock_timestamp()(なし)SYSDATE()(なし)

ほかのDBと同じ動きにしたいなら statement_timestamp() を使います。

ストアドの中で処理時間を測る

DO $$
DECLARE
  t0 timestamptz := clock_timestamp();
  t1 timestamptz;
BEGIN
  PERFORM pg_sleep(1);   -- 処理A
  t1 := clock_timestamp();
  RAISE NOTICE '処理A: % ms', round(extract(epoch FROM t1 - t0) * 1000, 3);

  PERFORM pg_sleep(0.5); -- 処理B
  RAISE NOTICE '処理B: % ms', round(extract(epoch FROM clock_timestamp() - t1) * 1000, 3);
END $$;
  • RAISE NOTICE の出力は、ロールバックしても消えません。測った結果をテーブルに INSERT すると、ロールバックのときに一緒に消えてしまいます。
  • サーバーログに残したいときは RAISE LOG を使います。JDBC からは Statement.getWarnings() で受け取れます。

注意すること

1. 列の DEFAULT やトリガーの now() に気をつける
created_at timestamp DEFAULT now()   -- トランザクション開始時刻が入る

-- SQL文の時刻にしたいなら
ALTER TABLE t ALTER COLUMN created_at SET DEFAULT statement_timestamp();

アプリのSQLを直しても、テーブル定義やトリガーの中に now() が残っていると、そこでトランザクション開始時刻が入ります。

また、DEFAULT に文字列の 'now' を書くのは間違いです。

CREATE TABLE d (
  a timestamptz DEFAULT 'now',   -- ×  CREATE TABLE した時刻で固定される
  b timestamptz DEFAULT now()    -- ○  INSERT したトランザクションの開始時刻
);

'now' は文字列として扱われるので、テーブルを作った時点の時刻がデフォルト値として固定されてしまいます。

2. WHERE 句の clock_timestamp() ではインデックスが使われない

clock_timestamp() は VOLATILE(呼ぶたびに値が変わる)な関数なので、インデックスを使った検索の条件になりません。now() と statement_timestamp() は STABLE(1つのSQL文の中では値が変わらない)なので、インデックスが使えます。

-- インデックスが使われない
WHERE updated_at > clock_timestamp() - interval '1 hour'

-- インデックスが使える
WHERE updated_at > statement_timestamp() - interval '1 hour'
3. clock_timestamp() の値は、重ならないとも順番どおりとも限らない

1マイクロ秒の間に何行も処理されると、同じ値になります。また NTP でサーバーの時計が合わせ直されると、時刻が戻ることがあります。一意キーや並び順には使わず、順番が必要ならシーケンスと組み合わせます。

4. JDBC の「トランザクション開始」は、最初のSQLを送ったとき

JDBC で setAutoCommit(false) を呼んだり、Spring の @Transactional に入ったりしただけでは、DBには何も送られません。PostgreSQL の JDBC ドライバーは、最初のSQLを送るときにまとめて BEGIN を送ります。ですから now() が返すのは、最初のSQLがDBに届いた時刻です。

なお autocommit=true のときは、SQL文1つが1つのトランザクションになるので、now() は実際には statement_timestamp() と同じ値になります。

関連記事

スポンサーリンク
タイトルとURLをコピーしました