DBD::SQLite 1.30_02
あと、先日リリースしたDBD::SQLite 1.30_02の変更点について。
DBD::SQLiteはこれまで
といった条件が重なったときにトランザクションがデッドロックすることがある、という問題を抱えていました。最小限の再現コードはこんな感じになります。
use strict; use warnings; use DBI; my $pid = fork(); if ($pid) { do_transaction(); } else { sleep 1; do_transaction(); exit; } unlink 'test.db'; sub do_transaction { my $dbh = DBI->connect('dbi:SQLite:test.db'); $dbh->do('create table if not exists foo (id)'); $dbh->begin_work; $dbh->do('select * from foo;'); $dbh->do('insert into foo values(1)'); sleep 2; $dbh->commit; }
これはSQLiteのマニュアル等にも載っている既知の問題で、対策としてはトランザクションを開始するときに BEGIN IMMEDIATE ないし BEGIN EXCLUSIVE といったSQLを発行することがあげられています。
sub do_transaction { my $dbh = DBI->connect('dbi:SQLite:test.db'); $dbh->do('create table if not exists foo (id)'); # $dbh->begin_work; $dbh->do('begin immediate'); $dbh->do('select * from foo;'); $dbh->do('insert into foo values(1)'); sleep 2; $dbh->commit; }
もちろんこれでもいいんですが、これだとO/Rマッパ等では無力なので、1.30_02では接続時に(ないし任意のタイミングで)データベースハンドルにsqlite_use_immediate_transactionというアトリビュートを渡すことで内部的に発行しているbeginをbegin immediateにできるようにした、というのが今回の変更点。
sub do_transaction { my $dbh = DBI->connect('dbi:SQLite:test.db'); $dbh->{sqlite_use_immediate_transaction} = 1; $dbh->do('create table if not exists foo (id)'); $dbh->begin_work; $dbh->do('select * from foo;'); $dbh->do('insert into foo values(1)'); sleep 2; $dbh->commit; }
DBD::SQLiteの用途的にはデフォルトで有効にしておいてもよさそうですが、この辺はもう少し余裕のあるときにO/Rマッパをつくっている人たちと相談してみるつもりです。