ようこそゲストさん

adiary開発日誌

お知らせ

絶対使わないと言い切れますか? blog + wiki = adiary

2009/08/09(日) PostgreSQLトランザクションとDBD::Pgの謎の挙動

PostgreSQLのトランザクションの仕様

CREATE TABLE test(x INT UNIQUE);
INSERT INTO test (x) VALUES (1);
INSERT INTO test (x) VALUES (2);

としておきます。

ここで、

 => BEGIN;
 => INSERT INTO test (x) VALUES (3);
INSERT 0 1
 => INSERT INTO test (x) VALUES (1);
ERROR:  duplicate key value violates unique constraint "test_x_key"

とするとエラーになります。PostgreSQLはトランザクション中にエラーが起こると、以後何をしてもエラーになり受け付けなくなります。ためしに続けて色々発行してみても、

 => INSERT INTO test (x) VALUES (10);
ERROR:  current transaction is aborted, commands ignored until end of transaction block
 => INSERT INTO test (x) VALUES (20);
ERROR:  current transaction is aborted, commands ignored until end of transaction block

こんな感じです。COMMITすると

 => COMMIT;
ROLLBACK

このようにROLLBACKされます。これがPostgreSQLの仕様です。

DBD::Pgの謎

  • 確認環境
    • PostgreSQL 8.3.7
    • DBD::Pg Ver1.49

DBD::Pgの場合

01: $dbh->begin_work;
02: $dbh->do('INSERT INTO test (x) VALUES (10)');
03: $dbh->do('INSERT INTO test (x) VALUES (1);');
04: $dbh->do('INSERT INTO test (x) VALUES (20);');
05: $dbh->commit;

とすると、3行目でエラーが起こり、4行目の実行でも"ERROR: current transaction is aborted, commands ignored until end of transaction block"といわれます。

これを、DBIのprepareを使用して

01: $dbh->begin_work;
02: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(10);
03: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(1);
04: $dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(20);
05: $dbh->commit;

とすると、3行目でエラーが起っても、4行目の実行が反映されてしまいます。謎の挙動です。

謎の解析

このようにソースを改変してログを取ってみました。

open(my $fh, ">pg_log.txt");
$dbh->pg_server_trace($fh);
$dbh->begin_work;
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(10);
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(1);
$dbh->prepare('INSERT INTO test (x) VALUES (?)')->execute(20);
$dbh->pg_server_untrace();
close($fh);

このときサーバに対して次のようなコマンドが発行されていました。

 => BEGIN;
 => INSERT INTO test (x) VALUES (10);
 => INSERT INTO test (x) VALUES (1);
エラー : duplicate key value violates unique constraint "test_x_key"
 => ROLLBACK;
 => BEGIN;
 => INSERT INTO test (x) VALUES (20);
 => COMMIT;

どうりで最後のCOMMITが成功するはずです。取得した生ログも置いておきます

これを、

01: $dbh->begin_work;
02: $dbh->prepare('INSERT INTO test (x) VALUES (10)')->execute();
03: $dbh->prepare('INSERT INTO test (x) VALUES ( 1)')->execute();
04: $dbh->prepare('INSERT INTO test (x) VALUES (20)')->execute();
05: $dbh->commit;

とすると再現しません。prepare中にUNIQUE制約をチェックして、勝手にrollbackしているようですが、原因がPostgreSQL側なのかDBD::Pg側なのかは絞り込めませんでした。

追記

DBD::Pgの実装仕様みたいです。トラックバックを参考にしてください(iakioさんに感謝)。

DBD::Pgを直すとすれば、プレースホルダが破棄されてもトランザクションが終了するまでDEALLOCATEするのを待つ、くらいでしょうが。

明示的なトランザクション内でのエラーとDEALLOCATE

ROLLBACKしていいから、DEALLOCATE後に空のトランザクションを begin して、失敗させてくれればそれで十分な予感。

メモ

  • どちらの場合も $dbh->commit(); の戻り値は "1" で成功している。

名前:   

  • TB-URL  http://adiary.blog.abk.nu/0257/tb/
  • [Perl][PHP]明示的なトランザクション内でのエラーとDEALLOCATE iakioの日記
    PerlのDBD::Pgで、明示的なトランザクション内の$dbh->prepareでエラーが発生すると、勝手にROLLBACKしてしまうそうです。 PostgreSQLトランザクションとDBD::Pgの謎の挙動 - adiary開発日誌 試してみ