iakioの日記 RSSフィード

2009-08-21

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

| 22:20 | 明示的なトランザクション内でのエラーとDEALLOCATE - iakioの日記 を含むブックマーク はてなブックマーク - 明示的なトランザクション内でのエラーとDEALLOCATE - iakioの日記

PerlのDBD::Pgで、明示的なトランザクション内の$dbh->prepareでエラーが発生すると、勝手にROLLBACKしてしまうそうです。

試してみたところ現象は再現しました。

元エントリのトレースログから、Query('Q')とParse('P')だけ抜き出してみると、

To backend> Msg Q
To backend> "begin"
To backend> Msg P
To backend> "dbdpg_27"
To backend> "INSERT INTO test (x) VALUES ($1)"
To backend> Msg Q
To backend> "DEALLOCATE dbdpg_27"
To backend> Msg P
To backend> "dbdpg_28"
To backend> "INSERT INTO test (x) VALUES ($1)"
To backend> Msg Q
To backend> "ROLLBACK"
To backend> Msg Q
To backend> "DEALLOCATE dbdpg_28"
To backend> Msg Q
To backend> "begin"
To backend> Msg P
To backend> "dbdpg_29"
To backend> "INSERT INTO test (x) VALUES ($1)"
To backend> Msg Q
To backend> "DEALLOCATE dbdpg_29"
To backend> Msg Q
To backend> "commit"

となっていて、実際にはParse、実行、DEALLOCATEを繰り返しています。このDEALLOCATEはプレースホルダが破棄された時に自動的に呼ばれるようなのですが、ABORTしたトランザクション内ではDEALLOCATEを実行できないので、勝手にROLLBACKしているようです。

なので、こんな風にプレースホルダがすぐには破棄されないようにすると、ROLLBACKしないようです。

$dbh->begin_work;
my $st1 = $dbh->prepare("insert into test(x) values (?)"); $st1->execute(10);
my $st2 = $dbh->prepare("insert into test(x) values (?)"); $st2->execute(1);
my $st3 = $dbh->prepare("insert into test(x) values (?)"); $st3->execute(20);
$dbh->commit;

DBD::Pgのソースコードの中にも、

dbdimp.c:3507
    /* If we are in a failed transaction, rollback before deallocating */
    if (PQTRANS_INERROR == tstatus) {

と書いてありました。

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

Rubyruby-pgPHPのpg_*ではプレースホルダをオブジェクトとして扱っていないので問題無さそうです。また、PHPのPDOではプレースホルダが破棄された時に自動的にDEALLOCATEを発行しますが、ROLLBACKは発行しないため、DEALLOCATEがエラーとなるようです。

<?php
$dbh = new PDO("pgsql:dbname=...");
$dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_WARNING);
$dbh->beginTransaction();
$dbh->prepare("insert into test(x) values(?)")->execute(array(10));
$dbh->prepare("insert into test(x) values(?)")->execute(array(1));
$dbh->prepare("insert into test(x) values(?)")->execute(array(20));
$dbh->commit();

nabenabe2009/08/23 02:47TBありがとうございます。ROLLBACKしていいから、DEALLOCATE後に空のトランザクションを begin して、内部的に失敗させてくれればそれで十分な予感。

PravalikaPravalika2012/10/08 00:16Hot damn, looking pretty uesful buddy.

whtbwqbrmpwhtbwqbrmp2012/10/09 13:40k6vtTC , [url=http://tfokhqkgvmok.com/]tfokhqkgvmok[/url], [link=http://wmmsericzehp.com/]wmmsericzehp[/link], http://bsgooojuolnj.com/

fmzzeycwfmzzeycw2012/10/10 21:36LcWUKx <a href="http://rhsvoumrprdr.com/">rhsvoumrprdr</a>

zyvcewjagrzzyvcewjagrz2012/10/11 11:12XbNpR2 , [url=http://htefyotokjvk.com/]htefyotokjvk[/url], [link=http://obhxihwsfztv.com/]obhxihwsfztv[/link], http://nukeywqmlfke.com/

トラックバック - http://postgresql.g.hatena.ne.jp/iakio/20090821

2009-06-29

PerlからPostgreSQLに非同期アクセスする方法の続き

| 01:17 | PerlからPostgreSQLに非同期アクセスする方法の続き - iakioの日記 を含むブックマーク はてなブックマーク - PerlからPostgreSQLに非同期アクセスする方法の続き - iakioの日記

ちょっと遅くなりましたが、指摘いただきありがとうございます。

はい。非同期問い合わせを使う場合は$sth->pg_readyは必須でした。私のコードだと、$sel->can_readになってもまだデータを読み込んだわけではないので、$sth->pg_resultのところではじめて読み込みにいってしまうことになります。

libpqのドキュメントはこちら

$sth->pg_readyはlibqpのPQconsumeInputとPQisBusyを呼び出しているようです。

あと、libpqの非同期問い合わせの最も有名な例はpgbenchですね。

DBD::Pgに何でlibpqのPQsetnonblocking相当が無いのかは知りません。ruby-pgにはあるようです。

これなんかも多分関連情報かと。

僕はこういうのを知っていてもコレといって使い道を思いつかないので、色んな人に知ってもらって、何か面白いことに応用してもらえるといいなーと思うわけです。

PrudencePrudence2011/08/20 16:49What an awesmoe way to explain this-now I know everything!

tblttoyugtblttoyug2011/08/22 22:52rkwxRL <a href="http://fgjnwxoybold.com/">fgjnwxoybold</a>

lwcfoxgclwcfoxgc2011/08/25 23:579qrOZg <a href="http://eexozfrolzmy.com/">eexozfrolzmy</a>

enzywzgjenzywzgj2011/08/31 17:36iFNs1h , [url=http://vcyhwguiehli.com/]vcyhwguiehli[/url], [link=http://nwkqhzozubqr.com/]nwkqhzozubqr[/link], http://atvgaozlneuc.com/

トラックバック - http://postgresql.g.hatena.ne.jp/iakio/20090629

2009-06-18

PerlからPostgreSQLに非同期アクセスする方法

| 00:33 | PerlからPostgreSQLに非同期アクセスする方法 - iakioの日記 を含むブックマーク はてなブックマーク - PerlからPostgreSQLに非同期アクセスする方法 - iakioの日記

、、、ずっと前から非同期でしたっ!!

リリース日: 1998-10-30

...

libpq now allows asynchronous clients(Tom)

リリース 6.4

ということでlibpqは10年前から非同期です。LLのライブラリがサポートするのはもうすこし後だったかもしれませんが。とりあえずPerlのDBD::PgRubyruby-pgは非同期問い合わせが使えると思います。あとPHPもできるかも。

$dbh->{pg_socket}でfdが取れるので、selectで待つこともできます。なんだけどPerl自信無いです。もちろんPOEとかわかりませんけど多分使えるんじゃないでしょうか。

(6/30追記:このコードにはあやまりがあります。トラックバック参照)

use strict;
use warnings;
use DBI;
use DBD::Pg ':async';
use IO::Handle;
use IO::Select;
use Data::Dumper;

my $dbh = DBI->connect("dbi:Pg:dbname=db1", "", "");
my $sth = $dbh->prepare("select 'Hello', pg_sleep(5)", {pg_async => PG_ASYNC});
$sth->execute;

my $sel = IO::Select->new();
$sel->add(IO::Handle->new_from_fd($dbh->{pg_socket}, 'r'));
until (my @ready = $sel->can_read(1)) {
    print "wait.\n";
}
$sth->pg_result;
print Dumper($sth->fetchall_arrayref);

参考

あと昔自分で書いたもの

2008-07-23

TheSchwartzをPostgreSQLで使うとworkerプロセスが太る件

| 00:40 | TheSchwartzをPostgreSQLで使うとworkerプロセスが太る件 - iakioの日記 を含むブックマーク はてなブックマーク - TheSchwartzをPostgreSQLで使うとworkerプロセスが太る件 - iakioの日記

TheSchwartz を PostgreSQL / SQLite で使うと worker プロセスが太る(のは解決済) - 酒日記 はてな支店

はてなブログ

今さらだけどわかった気がします。DOD_DEBUG=1してSQLを見てみると、

$ DOD_DEBUG=1 perl worker.pl dbi:Pg:dbname=dbname user pass
...
$VAR1 = 'SELECT job.jobid, job.funcid, job.arg, job.uniqkey, job.insert_time, job.run_after, job.grabbed_until, job.priority, job.coalesce
FROM job
WHERE (job.funcid IN (?)) AND (job.run_after <= 1216826760) AND (job.grabbed_until <= 1216826760)
LIMIT 50
';
$VAR2 = [
  '1'
];

となっていて、(job.run_after <= 1216826760)とか(job.grabbed_until <= 1216826760)の部分がプリペアードステートメントになっていない。本来は(job.run_after <= ?) AND (job.grabbed_until <= ?)となっていなければいけないのに。

ここの値は毎回変わるので、それを$dbh->prepare_cached($sql)してしまうと、どんどんキャッシュが増えてしまう、という話ではないかと。

で、多分これで直ると思います(多分というのはGTopとか使える環境が無かったので実際メモリがどうなってるのかは調べてないです)。

--- TheSchwartz.pm.org  2008-07-24 00:20:13.000000000 +0900
+++ TheSchwartz.pm      2008-07-24 00:37:15.000000000 +0900
@@ -210,8 +210,8 @@

             @jobs = $driver->search('TheSchwartz::Job' => {
                     funcid        => $funcid,
-                    run_after     => \ "<= $unixtime",
-                    grabbed_until => \ "<= $unixtime",
+                    run_after     => { op => "<=", value => $unixtime },
+                    grabbed_until => { op => "<=", value => $unixtime },
                     coalesce      => { op => $op, value => $coval },
                 }, { limit => $FIND_JOB_BATCH_SIZE });
         };
@@ -250,8 +250,8 @@

             @jobs = $driver->search('TheSchwartz::Job' => {
                     funcid        => \@ids,
-                    run_after     => \ "<= $unixtime",
-                    grabbed_until => \ "<= $unixtime",
+                    run_after     => { op => "<=", value => $unixtime },
+                    grabbed_until => { op => "<=", value => $unixtime },
                 }, { limit => $FIND_JOB_BATCH_SIZE });
         };
         if ($@) {

何故MySQLは大丈夫なのかは調べてません。

あと個人的な立場としてはPostgreSQLならPgQイイヨーと言っておく。

追記

どうやら上の話は全然関係なくて、DBD::Pgのバージョンに依存するようです。

DBD::Pg 1.49
0,14561280
100,20054016
200,22503424
300,24977408
400,27328512
500,29458432

DBD::Pg 2.8.6
0,14258176
100,17588224
200,17588224
300,17588224
400,17588224
500,17588224

iakioiakio2008/07/24 04:40あれ?やっぱ違う気がしてきたorz

iakioiakio2008/07/24 09:36実はDBD::Pgのバージョンが古いという話のような気がしてきた

EstellaEstella2012/01/12 05:29Ya learn somteihng new everyday. It's true I guess!

yrzifmagyrzifmag2012/01/12 18:31F5O21X <a href="http://tluonrirvyos.com/">tluonrirvyos</a>

upxckkgwpfaupxckkgwpfa2012/01/13 23:131Wbzhz , [url=http://yudvobletkii.com/]yudvobletkii[/url], [link=http://lhtpshoyerht.com/]lhtpshoyerht[/link], http://ugqgeozvihdv.com/

utflmiujhdutflmiujhd2012/01/14 20:222iWnBw <a href="http://trxlofhmogko.com/">trxlofhmogko</a>

odjxxyodjxxy2012/01/16 20:31SmGnKl , [url=http://qkpprgtznflm.com/]qkpprgtznflm[/url], [link=http://zbpvpgudxiuu.com/]zbpvpgudxiuu[/link], http://ibashjcszylb.com/

トラックバック - http://postgresql.g.hatena.ne.jp/iakio/20080723

2008-06-03

SQLインジェクションとDBD::PgPP

| 13:04 | SQLインジェクションとDBD::PgPP - iakioの日記 を含むブックマーク はてなブックマーク - SQLインジェクションとDBD::PgPP - iakioの日記

PostgreSQL8.1.4のリリースが2006年で、DBD::PgPPの0.05が2004年なので、standard_conforming_stringに対応していないのは当然といえば当然かもしれませんが。

こんどはPerlでの例。PerlからPostgreSQLを利用する場合には、DBIとDBD::Pgの組み合わせが利用される・・・と思うのだが、筆者の環境では中々DBD::Pgがインストールできなかったので、代わりにDBD::PgPPを使って検証してみた。PgPPはピュアPerlで記述されたPostgreSQL用インターフェースである。DBD::PgPP中のquote()のソースを見ると、文字の変換部は以下のようになっていた(バージョン0.05)。

徳丸浩の日記 - SQLインジェクション対策 - SQLエスケープにおける「\」の取り扱い

DBD::PgPPはもう使ってはいけないモジュールと言ってしまっていいかもしれなません。

「DBD::PgPP」の事例は脆弱性として取り扱われてもよいと思う。

はてなブックマーク - HiromitsuTakagiのブックマーク / 2008年06月03日

DBD::PgPPを使っている人はそう多くないのではないかと思いますが、Rubyのpostgres-prはかなり使われているのではないかと思います。これも同様の問題が発生すると思います(やっぱ2004年から更新されてないし)が、何をどうしろと言えるほど私はRubyを知りません。

  def self.escape(str)
    # TODO: correct?
    str.gsub(/\\/){ '\\\\' }.gsub(/'/){ '\\\'' }
  end

RaniRani2012/12/26 09:11This is way more hluepfl than anything else I've looked at.

trjoqntrjoqn2012/12/29 08:09tt57QG , [url=http://qkwgeephrern.com/]qkwgeephrern[/url], [link=http://xrhdurjpgorv.com/]xrhdurjpgorv[/link], http://snridnvpoaex.com/

トラックバック - http://postgresql.g.hatena.ne.jp/iakio/20080603