1つの接続で複数のファイルを扱う
SQLiteの接続は、1つのファイルに縛られているわけではありません。ATTACH DATABASEを使えば、最初に開いた.dbファイルに加えて別の.dbファイルもアタッチでき、まるで1つのデータベース内のスキーマのように、それらすべてに対してクエリを発行できます。SQLiteで「1サーバー上の複数データベース」に最も近い感覚を実現できる仕組みです。
基本構文はこちら:
archive.db ファイルは、メインデータベースと同じく、存在しなければ自動的に作成されます。このセッション中、archive. というプレフィックスを付けたものはすべてこの2つ目のファイルに、main. プレフィックス付き(またはプレフィックスなし)のものは元のファイルに保存されます。
接続には常に2つの暗黙的なスキーマが存在します。最初に開いたファイルを指す main と、一時テーブル用のスクラッチ領域である temp です。ATTACH すると、ここにさらに追加されるイメージです。
ATTACH DATABASE の構文とエイリアスの役割
ATTACH DATABASE 'path/to/file.db' AS alias_name;
エイリアスは、テーブルを修飾するときに使うスキーマ名です。これは現在の接続だけで有効なもので、同じファイルをアタッチする別の接続では違うエイリアスを使ってもかまいません。何度も入力することになるので、archive、analytics、cache のように短くて分かりやすい名前を選びましょう。
押さえておきたいポイントをいくつか:
- パスは絶対パスでない限り、プロセスのカレントディレクトリからの相対パスとして解釈されます。
- 文字列
':memory:'を指定すると、そのエイリアスで新しいインメモリデータベースがアタッチされます。 - エイリアスは
mainやtempと重複してはいけません。また、複数のアタッチで同じエイリアスを使うこともできません。
別DB同士をJOINする
ATTACH DATABASE を使う一番の動機がこれです。2つのファイルを同じ接続にまとめてしまえば、1つのクエリでそれぞれのテーブルをJOINできます:
クエリプランナは、アタッチした側のスキーマも main のテーブルとまったく同じように扱います。アタッチ先のテーブルに張ったインデックスもちゃんと使われますし、EXPLAIN QUERY PLAN もデータベースをまたいで動きます。両方のファイルは同じプロセス内で開かれているので、ネットワーク経由のやり取りも発生しません。
ホットデータとコールドアーカイブを分けたい場合、テナントごとにファイルを分割したい場合、あるいは読み取り専用のルックアップDBから参照データを引きたい場合など、実用的な場面は意外と多いです。
読み取り専用・インメモリでアタッチする
参照するだけで書き換えるつもりがないデータベース ―― たとえば製品に同梱したリファレンスデータなど ―― は、URI 形式で読み取り専用としてアタッチしておくと安心です。
URI形式を使うには、SQLiteライブラリ側でSQLITE_OPEN_URIが有効になっている必要があります(CLIやほとんどの言語バインディングではデフォルトで有効です)。これを使うと、ref.*に対するINSERT、UPDATE、DELETEはファイルに触れる前にエラーとして弾かれます。
ステージング用途には、インメモリDBのアタッチも同じくらい便利です。
scratch はコネクションを閉じた時点で消えます。temp と似たような挙動ですが、寿命を自分でコントロールできるのがポイントです。
トランザクションはアタッチした全DBにまたがる
BEGIN から COMMIT までのトランザクション 1 つで、main とアタッチ済みのすべてのスキーマへの書き込みをまとめて扱えます。すべてコミットされるか、すべてロールバックされるかのどちらかになるので、複数ファイルをまたいでも原子性はきちんと保たれます。
ライブテーブルからアーカイブファイルへ行を移すような処理は、まさにこのアトミック性の保証が欲しくなる場面です。複数ファイルをまたいだアトミック性がなければ、途中でクラッシュしたときに行が重複したり、最悪の場合は消えたりしてしまいます。
ひとつ注意点があります。同じトランザクション内で複数のアタッチ済みデータベースに書き込む場合、SQLite はより慎重なコミットプロトコルに切り替わり、一時的なジャーナルが必要になります。単一ファイルへのコミットよりは遅くなりますが、安全性はきちんと担保されます。
アタッチを解除する(DETACH DATABASE)
アタッチしたデータベースが不要になったら、忘れずに切り離しておきましょう。
DETACH DATABASE archive;
ファイル自体はディスク上にそのまま残ります。DETACH は現在の接続のハンドルを閉じるだけです。覚えておきたい制限は2つ:
mainとtempはデタッチできません。- トランザクション中、またはオープン中のステートメントが残っているデータベースもデタッチできません。
デタッチを忘れても致命的ではありません。接続を閉じればまとめて解放されます。
制限事項とよくあるエラー
押さえておきたい実用上の制限をいくつか:
- デフォルトでは1つの接続につきアタッチできるDBは10個までです(
mainとtempは別枠)。コンパイル時の上限は125です。これを超えるとtoo many attached databases - max 10が出ます。 - アタッチしたファイルごとにページキャッシュを持ちます。大きなデータベースを大量にアタッチするとメモリ使用量が増えるので無料ではありません。
ATTACH自体はトランザクション内では実行できません。BEGINの前かCOMMITの後に実行してください。
遭遇しがちなエラーをいくつか紹介します:
-- ファイルが存在せず、ディレクトリも書き込み不可:
Error: unable to open database: 'missing/path.db'
-- 読み取り専用のアタッチメントに書き込もうとした:
Error: attempt to write a readonly database
-- 同じエイリアスを2回使った:
Error: database archive is already in use
読めばだいたい意味は分かるはずですが、ハマりやすいのは「すでに使われています(already in use)」エラーです。ATTACH は既存のエイリアスを上書きしてくれないので、いったん DETACH してから貼り直す必要があります。
実用パターン:ホット/コールド分割
ここまでの内容を組み合わせて、1年以上前の注文をメインのDBから切り離してアーカイブする、簡単なワークフローを作ってみましょ��。
古いレコードは archive.orders に退避し、新しいものだけを main に残しておきます。履歴を参照したいレポートは両方の DB を JOIN すれば取得でき、普段のクエリはテーブルが小さくなった main.orders に対して実行されるので快適なまま。接続は 1 本、ファイルは 2 つ、トランザクションは 1 つにまとまります。
次のテーマ:プリペアドステートメント
ATTACH は、1 本の接続から複数のデータベースを扱うための仕組みでした。ここからは、アプリケーションが SQLite と安全かつ効率的にやり取りする方法に話を移します。最初に取り上げるのは、パラメータバインディングや SQL インジェクション対策の土台となる「プリペアドステートメント」です。
よくある質問
SQLiteのATTACH DATABASEは何をする命令ですか?
ATTACH DATABASE 'file.db' AS alias と書くと、現在の接続の中でもう1つのSQLiteファイルを開き、そこにスキーマ名を割り当てます。以降は alias.table_name の形でテーブルを参照でき、メインDBのテーブルと同じクエリの中でJOINすることもできます。
1つの接続でいくつまでデータベースをアタッチできますか?
デフォルトでは1接続あたり最大10個までで、これに加えて main と temp スキーマが使えます。ハード上限は125で、コンパイル時の SQLITE_MAX_ATTACHED で変更可能です。上限を超えると too many attached databases エラーが返ります。
アタッチした複数DBを1つのSQLで横断クエリできますか?
できます。アタッチ後はテーブル名にスキーマ名を付けて SELECT * FROM main.users JOIN archive.orders ON ... のように書くだけです。JOIN、サブクエリ、INSERT ... SELECT などすべてスキーマをまたいで動きます。トランザクションも全アタッチDBにまたがるので、COMMIT は複数ファイルに対して原子的に効きます。
アタッチしたデータベースはどう切り離せばいいですか?
DETACH DATABASE alias を実行します。ファイル本体には一切手が入らず、現在の接続からハンドルを閉じるだけです。ただし main と temp は切り離せません。トランザクションの途中にあるDBも同様にDETACHできない点に注意してください。