DBIx::Classで手動コミット

| コメント(2) | トラックバック(0)

研究でDBをいじる機会を得たので、せっかくだからperlのO/RマッパであるDBIx::Classを使ってみることにした。そして例によってハマったのでメモっておく。

各テーブルに対応したクラスをDBIx::Class::Schema::Loaderで自動生成して(マニュアルのワンライナー参照)
一緒に掃き出されたSchemaクラスに以下の一行を追加した。

__PACKAGE__->connection('dbi:Pg:dbname=ga;host=localhost', 'postgres', '', { AutoCommit => 0 });

optionでAutoCommit => 0としたのは複数のテーブルをupdateする際に手動でコミットしたかったから。多分そっちの方がエラーの処理が書きやすいかなと思った。後の地雷を作ることになる。

find等のメソッドが動くことを確認したので次に複数のテーブルにまたがるinsert処理を書いた。

抜粋するとこんな感じ


my $schema = GA::Schema->connect;
$schema->txn_begin;
(ここに一連のinsertやupdate処理)
$schema->txn_commit;

だが、実行してもDBのテーブルは変化せず。
$schema->storage->debug(1);
とやってデバッグメッセージでSQLをはき出してみたが、ちゃんと発行されてる。どうやらうまくコミットされてないようだ。

とりあえずDBIx::Class::Schemaのマニュアルを見てみる

Equivalent to calling $schema->storage->txn_begin. See "txn_begin" in DBIx::Class::Storage::DBI for more information.

てなワケでDBIx::Class::Storage::DBIの該当箇所のソースを読んでみる。
http://search.cpan.org/src/ASH/DBIx-Class-0.08006/lib/DBIx/Class/Storage/DBI.pmより

sub txn_begin {
  my $self = shift;
  $self->ensure_connected();
  if($self->{transaction_depth} == 0) {
    $self->debugobj->txn_begin()
      if $self->debug;
    # this isn't ->_dbh-> because
    #  we should reconnect on begin_work
    #  for AutoCommit users
    $self->dbh->begin_work;
  }
  $self->{transaction_depth}++;
}
sub txn_commit {
  my $self = shift;
  if ($self->{transaction_depth} == 1) {
    my $dbh = $self->_dbh;
    $self->debugobj->txn_commit()
      if ($self->debug);
    $dbh->commit;
    $self->{transaction_depth} = 0
      if $self->_dbh_autocommit;
  }
  elsif($self->{transaction_depth} > 1) {
    $self->{transaction_depth}--
  }
}

マニュアルではtxn_begin : Begins a transaction (does nothing if AutoCommit is off) っていってたけど嘘じゃん!どうやらtransaction_depthがコミットするか否かの鍵を握っているようだ。早速この値がどう変化しているのか調べてみる。

my $schema = GA::Schema->connect;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #1
$schema->txn_begin;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #2
(ここに一連のinsertやupdate処理)
$schema->txn_commit;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #1

AutoCommit => 0 のときはtxn_beginをやらなくても常に一段余計に重ねた部分にいるようだ。2の状態でtxn_commitしても発動しないので$schema->txn_begin;を消したら行けた。ただ、ちょっと綺麗じゃない気がしたのでAutoCommit => 1に変えてやってみた。

(AutoCommit => 1指定時)
my $schema = GA::Schema->connect;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #0
$schema->txn_begin;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #1
(ここに一連のinsertやupdate処理)
$schema->txn_commit;
print Data::Dumper::Dumper($schema->storage->{transaction_depth});      #0

これでもDBに登録できるのを確認した。こっちの方が綺麗だし
DBIx::Class::Storage::DBIのマニュアルにも

If you set AutoCommit => 0 in your connect info, then you are always in an assumed transaction between commits, and you're telling us you'd like to manage that manually. A lot of DBIC's magic protections go away. We can't protect you from exceptions due to database disconnects because we don't know anything about how to restart your transactions. You're on your own for handling all sorts of exceptional cases if you choose the AutoCommit => 0 path, just as you would be with raw DBI.

ってあるし、AutoCommit => 0は避けて、手動コミットしたくなったらSchemaクラスtxn_beginでトランザクションを開始した方が無難なのかねぇ。

トラックバック(0)

トラックバックURL: http://hoge.sub.jp/blog-cgi/mt/mt-tb.cgi/475

コメント(2)

http://search.cpan.org/~ash/DBIx-Class-0.08006/lib/DBIx/Class/Manual/Cookbook.pod#Transactions
ってことで

$schema->txn_do(sub {...});

がお奨めです。

なるほど。Cookbookだけでなく、DBIx::Class::Schemaのマニュアルを読み直したらtxn_doのとこにThis interface is preferred over using the individual methods "txn_begin", "txn_commit", and "txn_rollback" below.
と思いっきり書いてありましたね(´Д`;a)

この記事を書いていた当初はcode_ref渡しだとソースが読みづらくなっちゃうかなぁと敬遠してtxn_beginとtxn_commitをベタ書きしていたのですが、トランザクションをネストしてもうまく動くようなのでtxn_doの方がよさげですね。

コメントする

このブログ記事について

このページは、Lyoが2007年9月 4日 10:05に書いたブログ記事です。

ひとつ前のブログ記事は「Smart::Comments と Data::Dumper」です。

次のブログ記事は「YAML::SyckでYAMLを解析する」です。

最近のコンテンツはインデックスページで見られます。過去に書かれたものはアーカイブのページで見られます。

OpenID対応しています OpenIDについて
Powered by Movable Type 4.261