lockedで悩む人に見て欲しいロックの話IT系おじさんのチラシの裏2018年10月~プロフィールAUTHOR:とっちら書きたくなったときに書きたいことを書く雑記ブログ。IT系のネタが多いかも知れません。11:57とっちら1年前の記事に助けられました。14:28とっちらこの記事のお陰で60分録画できるようになりました。16:01とっちらライセンスを明示して貰えると使いやすいのですが。14:37ご回答いただきどうもありがとうございます。21:56とっちら超初心者です。)SQLiteを使ったサイトをいくつか運営しているのですが、時折発生する↓このエラーに頭を悩ませていました。IMMEDIATEを適切に使うことで解決しました。20を設定しておけば書き込みが衝突しても20秒待ってくれるだろ、と思い込んだり...。罠にハマりやすいと思います。今回はその誤解を解きほぐしていきましょう。lockedについて書かれているサイトやブログは多くありますが、SQLiteは読み込みは複数プロセスに対応しているものの、書き込みトランザクションは1本だけなので、同時書き込みに弱いとか、PVの多い大規模サイトでは別のDBを選択すべきとか書かれていることが多く、もう少し具体例を出してもらえないものか、と悩んでいました。現象が再現できれば対応策も考えやすいというものです。lockedが発生していましたが、1日数万PVのうちの1アクセスに過ぎないし、検索エンジンが頻繁にアクセスしてくるからそんなエラーが発生しているのかな、なんて呑気に考えていました。調べてみたら、そんな不確実な理由ではありませんでした。こちらのPHPソースをご覧ください。LOCK_EX);}このPHPスクリプトを使って現象を再現していきます。test.db、テーブル名はtestです。ここではテーブル構造は重要ではありませんが、サンプルで想定しているテーブルは↓こんな感じ。lockedをWindows上で再現する方法Linux上で開発してる方はごめんなさいね。ぼくはWindows上でPHPのコードを書き、CentOSでnginxあるいはLiteSpeedを使ったレンタルサーバー上にアップロードして使うスタイルです。ローカルにLinux環境を作ったほうが良いとは思うのですが、Windowsひとつで済んだほうが個人的には楽なため、なるべくテストもWindowsで完結するようにしています。Windows上にセットアップしたPHP7.4+nginxの環境で、どうやって複数ユーザーの同時アクセスを実現するか。最初はそうやっていました。Chromiumでも良いですが、ブックマークにフォルダを作成し、その中に複数のURLを登録すれば、マウスの中クリックもしくは右クリック+「すべてを開く」を実行することでほとんど同時にアクセスするシミュレーションが可能です。発生し、ん?今まで再現できないと思っていた現象が簡単に再現できたぞ...と驚き、本格的に調査を始めたのです。このテスト方法だと何十回、何百回と繰り返す作業に向かないため、コマンドラインで複数アクセスを実現することにしました。3こんなバッチファイルを用意するだけ。PHPをローカルで動かすのは驚くほど簡単です。Webサーバーすら必要ありません。下記のPHP公式サイトからWindows用のZIPをダウンロードしてCドライブのPHPディレクトリに解凍するだけ。https://windows.php.net/download#php-7.4php.iniは「extension=pdo_sqlite」だけ有効にすればOKです。いう名前で保存して実行してあげればOK。startコマンドは別プロセスで実行するという意味で、/Bオプションはそのときに新しいウィンドウを開かずに実行する、という意味です。test_lock.phpの後ろに1、2、3の番号を付けているのはそれぞれのプロセスを識別するため。ログファイルにプロセス番号と実行したコマンドを記録することで、どんな順番で動いているのか確認するわけですね。lockedが再現できました。ログだと見づらいかもなので、プロセス番号を横軸にして実行されたコマンドを順に見てみましょう。-はい、プロセス2番、3番は1行も書き込みに成功していませんね。ロックされているから。実行された時点でロックがかかります。EXCLUSIVEでトランザクションを開始したときだけです。lockedになります。同時ログインなんて器用な真似する人、アタッカー以外おらんやろ、と考えるかも知れませんが、自動ログイン処理を実装していた場合は普通にありえます。クッキーにトークンを保存しておき、自動ログインが成功するたびにトークンを更新するような仕組み。そのとき、ユーザーがお気に入りに複数登録していたり、あるいはブラウザのタブ復帰機能で同一サイトを同時に複数のタブで開いたり、同じユーザーが全く同一のタイミングでアクセスしてくることは普通にあるのです。lockedが発生していることがわかりました。解決策1:トランザクションを使わない乱暴なやり方ですが、更新順がどうでも良いのに雰囲気でトランザクションを使っているだけならこれが一番てっとり早いです。LOCK_EX);}最初のサンプルからBEGIN~COMMITを失くしただけですが、これを実行するとこんな順番で記録されます。5行目これは一例であり、実行するたびに行挿入される順番は異なります。どのプロセスも5行挿入できているからOK、というシステムならアリ。lockedは発生しなかったので、オートコミット時の書き込み衝突時は自動でロック解除を待ってくれるのかも。IMMEDIATEを使うたぶん、もっとも無難。IMMEDIATEにしただけなのでソースの再掲はしませんが、実行結果は下記のとおり。ENDプロセスの順番自体は1→3→2の順になっていますが、いずれかのプロセスが1~5行目まで書き込み完了するのを待ってから次のプロセスが動作しています。プロセス数を100個にしてもこの動きは変わりませんでした。IMMEDIATEを使うスクリプト同士が同時実行された場合は、片方の処理が終わるまで処理が止まりますが、トランザクションを使っていない、単なるSELECT文だけの処理は止まることなく動き続けます。IMMEDIATEの処理が完了していないのに、ほかのプロセスでINSERT/UPDATEなどの更新要求が発生した場合にはPDO::ATTR_TIMEOUTに設定された時間だけ待ち、その間にトランザクションが終了すればほかのプロセスの更新処理も引き続き動き出します。EXCLUSIVEの場合は他のプロセスに読み込みも許可しません。Webサイトに実装する場合ではEXCLUSIVEでのトランザクションはちょっと使いづらいのではないかなぁと思います。SQLiteはDBを分けるのも手軽ですから、EXCLUSIVEモードで更新する専用のdbを用意する等、活用方法はあると思いますけどね。まとめ実際にPHPで自動ログイン処理を実装したときにどんな問題が発生するのか、実例を交えて解説してみようと思っていたのですが、思いのほか記事が長くなったのでひとまず今回はここまでにしておきます。EXCLUSIVEでは別プロセスの読み取り操作もロックされてしまうため、更新順が重要な処理においても基本的にはIMMEDIATEのほうを使ったほうが良いと思われると、こんなところでしょうか。箇条書きにすると何か当たり前のことを書いている気がしますが、こんなことで1年近く悩んでいたので、もっと早く同時アクセスのサンプルコードを作ってテストしておけば良かったと反省しました。lockedが全く発生しなくなって快適そのものです。SQLiteを見直しました。画面いっぱいに表示される広告や、チカチカ光ったり、誤クリックを狙うような広告、凄まじくウザいですよね。コメント投稿直後ならコメントの編集/削除が可能です。※24時間経過するか、新しく投稿した場合は古い投稿の編集や削除は出来ません。※ブラウザでCookieが受け入れられない場合はこの機能は使えません。非公開にチェックを入れたコメントは管理人のみ閲覧できます。コメント投稿のガイドライン誰もが閲覧できるコメント欄ですので個人が特定できる情報等は書き込まないようにしましょう。他人のHPやブログの文章を掲載する場合は著作権法第32条「引用」の条件を満たすかご確認ください。誹謗中傷にあたる表現などは場合によっては法的な責任を問われる可能性があるためご注意ください。とっちらか~るTOP。