PDOのSQLiteでデータベースロックのタイムアウト値を設定する
以前、「PHPでSQLiteのロックとファイルロックの挙動をおさらい」でSQLiteのロックについて触れましたが、そのロックアウトが発生するまでの時間指定について書き忘れていたので、備忘録も兼ねてメモメモ。
何も指定しない場合、スクリプトのタイムアウトまで待つことになる
例えば次のようなPHPスクリプトでSQLiteのDBを40秒間ロックしたとします。
(BEGIN EXCLUSIVE TRANSACTIONは排他ロックの開始を意味します)
■サンプル1
<?php
$conn = new PDO('sqlite:test.db');
$conn->query("BEGIN EXCLUSIVE TRANSACTION");
sleep(40);
?>
↑このPHPスクリプトを実行中に ↓次のスクリプトを実行した場合
■サンプル2
<?php
$conn = new PDO('sqlite:test.db');
$conn->query("BEGIN EXCLUSIVE TRANSACTION");
echo "OK";
?>
最後に"OK"と表示しようとしていますが、実際にこれが表示されることはなくサンプル2のPHPスクリプトはタイムアウトします。
PHPのデフォルトのタイムアウト値が30秒なため、当たり前の挙動なのですが、じゃあサンプル1のsleepの秒数を変えたらどうでしょうか。
サンプル1のsleepを10秒にした場合は、サンプル2では10秒間待った後、"OK"が表示されることになります。
サンプル1でロックされたデータベースが解除されるのを待つためですね。
SQLiteでロックのタイムアウト値を指定する
サンプル1はそのままに、サンプル2を次のように書き換え、先述と同じようにサンプル1→サンプル2の順で実行します。
■サンプル2
<?php
$conn = new PDO('sqlite:test.db');
$conn->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$conn->setAttribute(PDO::ATTR_TIMEOUT, 1);
try {
$conn->query("BEGIN EXCLUSIVE TRANSACTION");
} catch (PDOException $e) {
//echo $e->getMessage();
}
echo "OK";
?>
今度はPDO::ATTR_TIMEOUTにタイムアウトする秒数を1秒と指定したため、サンプル1の実行完了を待つことなく、ERRMODE_EXCEPTIONが発動し、try~catchのcatch側を通ることに。
ちなみにコメントアウトしていますが、echo $e->getMessage();を有効にした場合、
SQLSTATE[HY000]: General error: 5 database is locked
と表示されます。
最初から時間のかかる書き込み処理がされるとわかっている場合はこうしてあえてロックタイムアウトさせることで、別の処理を実行できるわけですね。
まとめ
PDO+SQLiteでロックのタイムアウト値を指定するにはPDO::ATTR_TIMEOUTを使う、というだけの備忘録でした。
実際、よほど大量の書き込みや、数千から数万単位のユーザーが同時書き込みでもしない限りはここまでの処理をする必要はないかも知れません。
ただ、混雑時はいったん別の処理をしてから、再度書き込みを試すようなロジックにするのも面白そうだし、そもそも混雑していないときだけ集計処理をしたい、といった場合には普通に使えるかも。
SQLiteでアクセス解析系の処理を組んだとして、アクセス数を記録しつつ、時間帯別集計はロックが発生していないときだけ実行する、みたいな。
んー、でも、それなら最初から集計用のDBを別にしておけばそもそもロック発生しないからそっちのほうが良いのかな…。
ともあれ、格安サーバーを使っていると数百ものサイトが1つのサーバーに詰め込まれている場合もあり、MySQLが落ちることもめずらしくないんですよね。
最近そんなトラブルが相次いだので、ユーザー数がさほど多くないサイトはSQLiteへ移行しちゃおっかなと考え中でして、DBロックまわりの挙動を再確認してみました。