DBIとforkの関係

実際ググれば正解はいっぱい出てくるしここに自分もコメントで書いてたりしていまさら書く必要もないかなと思ってたけど一応自分のブログでもまとめておくということで。

一般的な解

DBIx::ConnectorとかDBIx::Handler経由でかならず$dbhを取得してからDBIを使う。 もしくはfork-safeなORM(DBIx::Class, DBIx::Skinny, Teng)を使う。

DBIを直接使っている場合

一般的なコネクションを保持するクライアントと同様にDBIもforkした子供が親のコネクションをそのまま使うことはバグの原因になります。特にトランザクションの処理等で重大な問題が起こる可能性がある。 解決策は、

  1. DBIのコネクションを親で作らないで、子供で独自に作る
  2. 親で作ってしまったコネクションを子供が安全にDESTROYし、再接続する

のどちらかになります。ここで問題は2で「安全にDESTROY」しないとどうなるかというと、DBIはDESTROY時に自動的にdisconnectするようになっているので子供が勝手に親の接続を切ってしまい、その後親がその接続を使おうとすると使えないという事が起こる場合がある。(DBDによっても違うので「場合」としている)

安全にDESTROYするには、親で$dbh->{AutoInactiveDestroy} = 1;を設定する(> DBI 1.6.14)か、子で$dbh->{InactiveDestroy} = 1;を設定する必要がある。

で、再接続は$dbh->cloneを使うと楽。まとめるとこんな感じがおすすめですね。

use DBI;
my $dbh = DBI->connect('dbi:mysql:database=sandbox;host=localhost', 'sandbox', 'sandbox');

if ( fork ) {
    # 親
    wait;
    my $row = $dbh->selectrow_hashref('select * from sandbox limit 1');
}
else {
    # 子
    $dbh->{InactiveDestroy} = 1;
    $dbh = $dbh->clone({InactiveDestroy => 0}); # 新しく作成する$dbhはactive destroyでおk
    my $row = $dbh->selectrow_hashref('select * from sandbox limit 1');
}

 追記: 上記コード例のs/$dbh->clone;/$dbh->clone({InactiveDestroy => 0})/