プログラミング
SQLiteデータベースファイルを破損させる方法
How to corrupt an SQLite database file (sqlite.org)
要約
SQLiteは高い耐障害性を持つデータベースですが、特定の条件下では破損する可能性があります。本記事では、ファイルの上書き、ファイルロックの問題、同期の失敗、ディスクドライブの故障、メモリ破損、OSの問題、SQLiteの設定エラー、SQLite自体のバグなど、SQLiteデータベースが破損する可能性のある様々なケースについて詳細に解説しています。各問題には具体的な例と、場合によってはSQLiteのバージョンアップによる対策も記されています。
全文翻訳
小さく。速く。信頼できる。その3つをすべて選べます。
ホームメニュー 概要 ドキュメント ダウンロード ライセンス サポート 購入 検索 概要 ドキュメント ダウンロード サポート 購入 検索 ドキュメント 検索 変更履歴 SQLiteデータベースファイルを破損させる方法 目次 1. 不正なスレッドまたはプロセスによるファイルの上書き 1.1. ファイル記述子が閉じられた後も継続して使用する 1.2. トランザクションがアクティブな状態でのバックアップまたはリストア 1.3. ホットジャーナルの削除 1.4. データベースファイルとホットジャーナルの不適切なペアリング 2. ファイルロックの問題 2.1. ロックの実装が壊れている、または欠落しているファイルシステム 2.2. close()を実行する別のスレッドによってキャンセルされるPosixアドバイザリロック 2.3. 同じアプリケーションにリンクされたSQLiteの複数のコピー 2.4. 異なるロックプロトコルを使用する2つのプロセス 2.5. 使用中にデータベースファイルをアンリンクまたはリネームする 2.6. 同じファイルへの複数のリンク 2.7. fork()を介して開いているデータベース接続を引き継ぐ 3. 同期の失敗 3.1. 同期要求に応じないディスクドライブ 3.2. PRAGMAを使用した同期の無効化 4. ディスクドライブとフラッシュメモリの故障 4.1. パワーセーフではないフラッシュメモリコントローラ 4.2. 偽の容量のUSBスティック 5. メモリ破損 6. その他のオペレーティングシステムの問題 6.1. Linuxスレッド 6.2. QNXでのmmap()の失敗 6.3. ファイルシステムの破損 7. SQLite設定エラー 8. SQLiteのバグ 8.1. WALモードデータベースへの書き込み時の競合状態 8.2. 古い式インデックス 8.3. データベースの縮小による誤った破損報告 8.4. ロールバックモードとWALモード間の切り替え後の破損 8.5. ロック取得中のI/Oエラーによる破損 8.6. データベースページがフリーページリストから漏洩する 8.7. 3.6と3.7からの交互書き込み後の破損 8.8. Windowsシステムでのリカバリ時の競合状態 8.9. ネストされたトランザクションで使用されるセカンダリジャーナルの境界値エラー
概要
SQLiteデータベースは、破損に対して高い耐性を持っています。アプリケーションのクラッシュ、オペレーティングシステムのクラッシュ、さらには停電がトランザクションの途中で発生した場合でも、部分的に書き込まれたトランザクションは、次にデータベースファイルにアクセスされたときに自動的にロールバックされるはずです。リカバリプロセスは完全に自動であり、ユーザーやアプリケーションによるいかなる操作も必要ありません。
SQLiteはデータベースの破損に強いですが、完全に無縁ではありません。このドキュメントでは、SQLiteデータベースが破損する可能性のあるさまざまな方法について説明します。
1. 不正なスレッドまたはプロセスによるファイルの上書き
SQLiteデータベースファイルは通常のディスクファイルです。つまり、どのプロセスでもファイルを開いて、ごみで上書きすることができます。SQLiteライブラリはこれを防ぐために何もできません。
1.1. ファイル記述子が閉じられた後も継続して使用する
ファイル記述子がファイルに対して開かれ、そのファイル記述子が閉じられ、SQLiteデータベース上で再度開かれるという複数のケースを見てきました。その後、他のスレッドが古いファイル記述子に書き込みを続け、元のファイルがすでに閉じられていることに気づいていませんでした。しかし、ファイル記述子がSQLiteによって再開されたため、元のファイルに入るはずだった情報がSQLiteデータベースの一部を上書きしてしまい、データベースの破損につながりました。
この一例は、2013年8月30日頃にFossil DVCSの正規リポジトリで発生しました。このイベントでは、ファイル記述子2(標準エラー)がsqlite3_open_v2()の前に誤って閉じられ(stunnelが原因と疑われます)、リポジトリデータベースファイルに使用されるファイル記述子が2になりました。その後、アプリケーションのバグにより、assert()ステートメントがwrite(2,...)を呼び出すことでエラーメッセージを出力しました。しかし、ファイル記述子2はデータベースファイルに接続されていたため、エラーメッセージがデータベースの一部を上書きしました。
この種の問題を防ぐため、SQLiteバージョン3.8.1(2013-10-17)以降は、データベースファイルに低い番号のファイル記述子を使用することを拒否します。(詳細についてはSQLITE_MINIMUM_FILE_DESCRIPTORを参照してください。)
閉じられたファイル記述子の使用による破損のもう1つの例は、2014年8月12日のブログ投稿でFacebookのエンジニアによって報告されました。このエラーの別の例は、2019年7月11日にFossilに対して報告されました。デバッグ出力用にファイル記述子が開かれましたが、その後SQLiteによって閉じられ、再度開かれました。しかし、デバッグロジックは元のファイル記述子への書き込みを続けました。バグレポートと修正へのリンクについては、フォーラムの議論を参照してください。
1.2. トランザクションがアクティブな状態でのバックアップまたはリストア
バックグラウンドで自動バックアップを実行するシステムは、SQLiteデータベースファイルがトランザクションの途中にいる間にバックアップコピーを作成しようとすることがあります。この場合、バックアップコピーには古い内容と新しい内容が混在する可能性があり、その結果破損します。
SQLiteデータベースのバックアップコピーを作成するための複数の安全なアプローチがあります。これらは、正しく破損していないバックアップを生成するという意味で安全です。順不同ですが、以下の通りです。
sqlite3_rsyncユーティリティプログラム(SQLite 3.47.0 (2024-10-21)以降で利用可能)は、帯域幅効率の良いプロトコルを使用してSSH経由で稼働中のSQLiteのコピーを作成します。
VACUUM INTO filenameコマンドは、SQLiteデータベースの現在の状態を別のファイルにコピーします。
バックアップAPIは、SQLiteデータベースの一貫性のあるコピーを作成できるC言語インターフェースです。
上記のいずれのアプローチも、稼働中のデータベースでも機能します。コピー中にトランザクションが進行していなければ、SQLiteデータベースファイルをコピーすることも安全です。
以前の書き込みトランザクションが失敗した場合、ロールバックジャーナル(*-journalファイル)または書き込み先行ログ(*-walファイル)がデータベースファイル自体と一緒にコピーされることが重要です。
1.3. ホットジャーナルの削除
SQLiteは通常、すべてのコンテンツを単一のディスクファイルに保存します。ただし、トランザクションの実行中に、クラッシュや停電後のデータベースのリカバリに必要な情報が補助ジャーナルファイルに保存されます。このようなジャーナルファイルは「ホット」と呼ばれます。ジャーナルファイルは、元のデータベースファイルと同じ名前に-journalまたは-walサフィックスが付いたものです。SQLiteは、クラッシュや停電からリカバリするためにジャーナルファイルを見る必要があります。クラッシュや停電後にホットジャーナルファイルが移動、削除、またはリネームされた場合、自動リカバリは機能せず、データベースが破損する可能性があります。
この問題のもう1つの現れは、8+3ファイル名の一貫性のない使用によって引き起こされるデータベースの破損です。
1.4. データベースファイルとホットジャーナルの不適切なペアリング
前の例は、より一般的な問題の具体的なケースです。SQLiteデータベースの状態は、データベースファイルとジャーナルファイルの両方によって制御されます。静止状態では、ジャーナルファイルは存在せず、データベースファイルのみが重要です。しかし、ジャーナルファイルが存在する場合、破損を避けるためにデータベースと一緒に保持する必要があります。
以下の行為はすべて破損につながる可能性があります。
異なる2つのデータベース間でジャーナルファイルを交換する。
ジャーナルファイルを別のジャーナルファイルで上書きする。
ジャーナルファイルをあるデータベースから別のデータベースに移動する。
ジャーナルもコピーせずにデータベースファイルをコピーする。
元のデータベースに関連付けられているホットジャーナルを削除せずに、あるデータベースファイルを別のデータベースファイルで上書きする。
2. ファイルロックの問題
SQLiteは、同時実行プロセス間のアクセスを調整するために、データベースファイルと書き込み先行ログ(WALファイル)にファイルロックを使用します。調整がなければ、2つのスレッドまたはプロセスが同時にデータベースファイルに互換性のない変更を加えようとし、データベースの破損を引き起こす可能性があります。
2.1. ロックの実装が壊れている、または欠落しているファイルシステム
SQLiteは、ドキュメントに記載されているとおりにロックを行うために、基盤となるファイルシステムに依存しています。しかし、一部のファイルシステムにはロックロジックにバグがあり、ロックが常に宣伝どおりに動作しないことがあります。これは特にネットワークファイルシステム、特にNFSに当てはまります。ロックプリミティブにバグがあるファイルシステムでSQLiteを使用し、2つ以上のスレッドまたはプロセスが同時に同じデータベースにアクセスしようとする場合