エラーは SQLite からのメッセージだと考えよう
SQLite のエラーメッセージは短くて、ときどき意味が掴みにくいですが、実際のところ原因は限られた数のパターンに集約されます。本番環境で遭遇する sqlite エラーのほとんどは、ロック、権限、破損、スキーマ不一致、制約違反という5つのカテゴリに分類できます。このページでは、それぞれのケースについて、何がきっかけで起きるのか、本当は何を意味しているのか、そしてどう直せばいいのかを順番に見ていきます。
エラーメッセージは数値コードとセットで返ってきます(拡張コードを使えばさらに具体的に原因を特定できます)。ログでは次のように両方の形式が出てきます:
Error: database is locked -- コード 5 (SQLITE_BUSY) — データベースがロックされています
Error: unable to open database -- コード 14 (SQLITE_CANTOPEN) — データベースを開けません
Error: attempt to write a readonly -- コード 8 (SQLITE_READONLY) — 読み取り専用への書き込みを試みました
Error: database disk image is -- コード 11 (SQLITE_CORRUPT) — データベースのディスクイメージが破損しています
コードを覚えておくと検索がはかどります。プレーンな英語メッセージで調べるより、SQLITE_BUSY で検索したほうが断然いい結果にたどり着けます。
database is locked(SQLITE_BUSY)エラーの対処法
複数箇所から書き込みを行うアプリで、SQLite を使うと真っ先に遭遇するのがこのエラーです。SQLite は書き込みを直列化する仕組みになっていて、書き込みロックを保持できるのは同時に1つのコネクションだけ。2つ目の書き込み側が busy timeout の時間内にロックを取れないと、この database is locked エラーが返ってきます。
効果が大きい順に、3つの対処法を紹介します。
WAL モードに切り替えるだけでも、ほとんどのワークロードでロック問題は解消します。それでも実際に書き込みが競合したときの保険になるのが busy timeout です。設定を見直したら、次はコードの監査です。トランザクションを開いたまま裏でネットワーク I/O を走らせると、その間ずっとロックを握り続けることになります。トランザクションは短く保ち、処理が終わったらすぐに COMMIT(または ROLLBACK)しましょう。
unable to open database file (SQLITE_CANTOPEN) エラーの対処
SQLite がファイルを開こうとしたものの、OS から拒否された状態です。原因の 95% はファイルパスかその親ディレクトリにあります。
-- 確認すべきこと:
-- 1. パスは存在するか? ls -l /path/to/db.sqlite
-- 2. 親ディレクトリは存在するか? SQLite はファイルは作成するが、
-- その上位のディレクトリは作成しない。
-- 3. プロセスを実行しているユーザーがディレクトリ
-- (ファイルだけでなく) に対して読み書き権限を持っているか?
-- 4. ボリュームはマウントされていて、満杯でなく、読み取り専用でないか?
微妙なハマりどころとして、SQLite はデータベース本体の隣に -journal、-wal、-shm といったサイドカーファイルを作る必要があります。つまり、ファイル自体に書き込み権限があっても、置いてあるディレクトリに書き込み権限がないと、オープンは通るのに書き込みで落ちるという現象が起きます。ディレクトリ単位で書き込み権限を付与することを忘れずに。
attempt to write a readonly database(SQLITE_READONLY)エラー
これは先ほどのエラーと近い親戚です。ファイルのオープンは成功しているのに、書き込み時にエラーになるパターン。よくある原因を頻度順に並べると以下のとおりです。
- OS ユーザーがそのファイル、またはディレクトリへの書き込み権限を持っていない。
- 接続を読み取り専用フラグ(
SQLITE_OPEN_READONLY、または URI 指定のmode=ro)で開いてしまっている。 - ボリュームが読み取り専用でマウントされている(Docker のバインドマウントや、一部のクラウドストレージでよくあります)。
- データベースが、SQLite に必要なロック機構をサポートしないネットワークファイルシステム上に置かれている。
権限を直すか、ボリュームを再マウントしてください。Docker を使っている場合は、バインドマウントが :ro になっていないか、コンテナユーザーがそのディレクトリを所有しているかを確認しましょう。
database disk image is malformed (SQLITE_CORRUPT)
ファイルのバイト列が SQLite のフォーマットと一致しなくなった状態です。原因の多くは環境側にあります。fsync が効かないファイルシステムで書き込み中にプロセスが kill された、書き込み中のデータベースをそのままコピーした、ハードウェア障害、あるいは Dropbox や iCloud でファイルを同期してしまった、といったケースが典型です。
まずは破損の状況を確認しましょう。
integrity_check の結果が ok であれば、データベース自体は問題ありません。エラーの原因は別のところ(よくあるのは古い接続が残っているケース)にあります。一方、問題のリストが返ってきた場合は復旧作業が必要です。
もっとも確実な復旧方法は、CLI の .recover コマンドを使うことです。読み取れるデータをできる限り吸い上げて、新しいデータベースに書き出してくれます:
sqlite3 corrupt.db ".recover" | sqlite3 recovered.db
sqlite3 recovered.db "PRAGMA integrity_check;"
直近のバックアップがあるなら、復旧よりもそちらから戻したほうが速いですし、「だいたい戻った気がする」みたいな曖昧さも避けられます。稼働中のデータベースを正しくコピーする方法は、バックアップとリストアのページを参照してください(ヒント: cp はNGです)。
no such table と no such column エラー
このsqlite エラーは文字どおりの意味なのですが、原因はだいたい次の2つのどちらかです。思っているのとは別のデータベースに接続している、もしくはマイグレーションが走っていない、のいずれかです。
アプリの接続文字列をまず確認しましょう。相対パスは「現在の作業ディレクトリ」を基準に解決されますが、これがターミナル、IDE、本番プロセスでそれぞれ違うため、思わぬ落とし穴になります。:memory: を指定したインメモリDBは毎回まっさらな状態で起動するので、「データが残っているはず」と思い込んでいるとハマりがちです。
識別子のクォートにも注意が必要です。クォートなしのテーブル名・カラム名は大文字小文字を区別しませんが、"User" と "user" はクォートで囲むと別物として扱われます。テーブル作成時にクォート付きで定義したなら、参照するときも必ずクォートで囲むようにしてください。
制約違反 (UNIQUE constraint failed など)
SQLite は制約に反する書き込みを拒否します。エラーメッセージには違反した制約の種類がはっきり出ます。
それぞれの失敗には、内部的に異なるエラーコードが割り当てられています(SQLITE_CONSTRAINT_UNIQUE、SQLITE_CONSTRAINT_CHECK、SQLITE_CONSTRAINT_NOTNULL)。対処法はほとんどの場合アプリケーション層にあります。書き込み前に入力値をバリデーションするか、重複を意図的に扱いたいなら INSERT ... ON CONFLICT を使いましょう。
FOREIGN KEY constraint failed については補足が必要です。SQLite では外部キー制約はデフォルトで無効になっています。有効化しないままだと、不正な参照がエラーも出さずに入り込み、後から制約を有効にした瞬間に一気に問題が噴き出します。接続ごとに必ず以下の PRAGMA を設定してください。
cannot start a transaction within a transaction
すでにトランザクションが開いている状態で BEGIN を実行すると、このエラーが出ます。SQLite はトランザクションのネストには対応していませんが、SAVEPOINT を使えば同じことが実現できます。
ORM やフレームワークにトランザクション管理を任せている場合、知らないうちに二重で BEGIN を発行しているケースが多いです。autocommit が有効になっているか、コネクションプールがすでにトランザクションを開いた状態のコネクションを使い回していないかを確認してみてください。
disk I/O error (SQLITE_IOERR)
OS が読み書きを拒否したときに出る sqlite エラーです。ディスクの空き容量不足、ネットワークファイルシステムの不調、あるいは SQLite が掴んでいるファイルが横から削除された、といった原因が考えられます。まずは df -h で空き容量を確認しましょう。次に疑うのは、データベースの置き場所が NFS やクラウド同期フォルダのような不安定な場所になっていないかです。SQLite は fsync がきちんと動くローカルの POSIX ファイルシステムを前提にしているので、こうした場所では破損リスクが跳ね上がります。どうしても移動できないなら、そのリスクは受け入れるしかありません。
syntax error near "..."
SQLite のパーサーは、どのトークンでつまずいたかを丁寧に教えてくれます。ただし、本当の原因はエラー箇所の3行ほど手前にあることがほとんどです。よくあるパターンは、カンマの抜け、キーワードと衝突する識別子をクオートしていない、あるいは文字列内のシングルクォートをエスケープし忘れている('it's' ではなく 'it''s' と書く)といったものです。
ユーザー入力を扱うときは、SQL を文字列連結で組み立てず、必ずパラメータバインディング(? プレースホルダー)を使ってください。これだけで構文エラーの大半と SQL インジェクションをまとめて回避できます。
トラブルシューティング用チェックリスト
本番環境で何かおかしくなったとき、次の順番で確認していけば、ほとんどのケースは 1 分以内に原因が絞り込めます。
PRAGMA は5つ、答えも5つ。失敗したクエリのエラーコードと組み合わせれば、その問題がどのカテゴリに属するのか、次にドキュメントのどのページを開けばいいのかがすぐにわかります。
カリキュラムのまとめ
ここまでが一通りのツアーです。CREATE TABLE から始まり、JOIN、インデックス、トランザクション、WALモード、バックアップ、そして最後に SQLite を実運用に投入したときに出くわす失敗パターンまで触れてきました。出てくるパターンは結局のところ同じで、トランザクションは短く、外部キーは有効化、WALモードを使い、バックアップは定期的に、そして PRAGMA integrity_check への適度な敬意を忘れないことです。この習慣さえ守っていれば、SQLite は何年でも黙々と動き続けてくれます。
よくある質問
SQLiteで「database is locked」と出るのはなぜ?
別の接続が書き込みロックを持っていて、こちら側の待機がタイムアウトしているのが原因です。対策としては、PRAGMA journal_mode=WAL でWALモードを有効にして読み込みと書き込みがブロックし合わないようにする、PRAGMA busy_timeout = 5000 でビジータイムアウトを延ばす、そしてトランザクションを開きっぱなしにせず素早くコミットする、の3点が定番です。
「attempt to write a readonly database」を直すには?
ほとんどの場合SQLite側ではなく、ファイルシステムのパーミッションの問題です。プロセスを実行しているOSユーザーに、DBファイル本体だけでなく、それを置いているディレクトリへの書き込み権限が必要です(SQLiteは同じ場所に -journal や -wal のサイドカーファイルを作るため)。所有者・パーミッションビット・ボリュームがread-onlyでマウントされていないかを確認してください。
「database disk image is malformed」とはどういう意味?
SQLiteが想定しているフォーマットと一致しないバイト列を読み込んでしまった、つまり破損です。プロセスの強制終了、ディスク不良、開いたままファイルをコピーしたといった原因が典型的です。まずは PRAGMA integrity_check で破損を確認し、CLIの .recover で救出可能なデータを新しいDBにダンプします。バックアップがあるなら、そこから戻すのが一番早いです。
「no such table」や「no such column」が出るのはなぜ?
思っているのとは別のDBファイルに繋がっているか、マイグレーションが流れていないかのどちらかです。PRAGMA database_list で実際にSQLiteが開いているファイルパスを確認し、.schema テーブル名 で実在のカラムを見てみましょう。識別子のタイプミスや大文字小文字の不一致もありがちです。SQLiteはクォートしていない名前なら大文字小文字を区別しませんが、クォート付きだと区別する点にも注意してください。