Daily-It

개발, AI, 인프라, 자동화와 일상 IT 제품 후기를 직접 써보며 정리하는 기술 블로그입니다.

SQLite とは?サーバーレスなファイルベースDBの利点・制約・実践的な使いどころ

概要

SQLite は、アプリ プロセス内で直接実行される、サーバーレスのファイルベースのリレーショナル データベースエンジンです。よく「軽量」と言われますが、それは単なるキャッシュやおもちゃのデータベースであるという意味ではありません。 SQLite はトランザクション、インデックス、JOIN、WAL、外部キー、STRICT テーブルをサポートしており、モバイル アプリ、デスクトップ アプリ、組み込みデバイス、テスト環境、小規模サービスで一般的に使用されています。

SQLite を選択するときの本当の問題は、「十分強力かどうか」ではありません。むしろ、「データが主に 1 つのアプリインスタンス、1 台のデバイス、または 1 つのファイルに収まるか」が判断材料になります。答えが「はい」の場合、SQLite はインストールと運用のコストを大幅に削減できます。逆に、複数のサーバーが高い同時実行性で同じ中央データベースに書き込む場合は、通常、PostgreSQL または MySQL の方が安全です。

この記事の内容

SQLiteとは

SQLite は、埋め込みSQLデータベースエンジン。アプリは直接リンクして呼び出します sqlite3 ライブラリ、データはデータベース ファイルに保存されます。 MySQL や PostgreSQL などの別のデータベース サーバー プロセスへの TCP 接続は必要ありません。

SQLite の公式ドキュメントでは、これを「自己完結型、サーバーレス、設定不要のトランザクション SQL データベースエンジン」と説明しています。実際の開発の観点からは、次のように理解できます。

特性 意味 実際的なメリット
Serverless 別個のデータベースサーバーは必要ありません 導入、インストール、メンテナンスの負担が少ない
Self-contained ほとんどの機能はライブラリ自体に含まれています モバイル、デスクトップ、CLI、オンデバイス ソフトウェアに簡単に組み込むことができます
Zero-configuration サーバー側の構成はほとんど、またはまったく必要ありません テスト、ローカル開発、サンプル アプリやガジェットに適しています
Transactional ACIDトランザクションをサポート ファイルデータベースもアトミックに変更可能
Public domain パブリックドメインライセンス 個人および商用プロジェクトの使用負荷が低い

SQLite は、高レベルのライブラリやフレームワークの背後に隠れていることもよくあります。たとえば、以前の WatermelonDB の記事, SQLite は、事実上のローカル ストレージ レイヤーとしてよく使用されます。 Android Room と iOS Core Data の SQLite バックエンドは同様の構造を持っています。開発者は上位層 API を使用しますが、データは最終的に SQLite データベース ファイルに保存されます。

SQLite が適しているシナリオと適さないシナリオ

最初の基準は単純です。データは主に 1 つのアプリ インスタンスまたは 1 つのデバイスの周りを流れますか? データベースがローカルでファイル中心の場合は、SQLite が適しています。複数のサーバー インスタンスへの同時大量書き込みを必要とする中央データベースの役割を担うには適していません。

SQLite が適している場所

  • モバイルアプリ用のローカルストレージ
  • デスクトップ アプリ、Electron アプリ、および CLI ツールの設定と状態の保存
  • IoT および組み込みデバイスのローカルデータ ロギング
  • テストデータベース、デモアプリ、ローカル開発環境
  • ほとんどが読み取りであるか、書き込み競合が少ない小規模なサービス
  • アプリファイル形式など、単一のファイルとして移動する必要があるデータ形式

オフラインファーストのアプリはその代表的な例です。ネットワークが切断されても、アプリはデータの読み取りと書き込みを続行する必要があります。 SQLite はサーバーへの依存がなく、データがアプリに近いため、この構造に適しています。

SQLite の利用に注意が必要な状況

  • 複数のサーバー インスタンスが同じデータベース ファイルに同時に書き込む必要がある
  • サービスでは引き続き 1 秒あたりの書き込みトランザクション数が増加しています
  • データベースサーバーレベルのユーザー、権限、アクセス制御、ネットワーク管理が必要
  • データベース ファイルを NFS や SMB などのネットワーク ファイル システムに配置したいと考えています。
  • レプリケーション、フェイルオーバー、運用監視は中核的な要件です

これは SQLite が「小さくて弱い」という問題ではなく、役割の問題です。ローカル ファイル データベースとしては、SQLite が強力です。しかし、PostgreSQL や MySQL の中央サーバー データベースの役割を強制的に置き換えると、ロック、バックアップ、アクセス許可、同時実行性の問題がすぐに発生します。

ファイル、トランザクション、型、WAL

データベースファイル

SQLite データベースは通常、 .sqlite または .db 書類。テーブル、インデックス、トリガー、ビューはすべてこのファイル内にあります。これは移動には非常に便利ですが、データベースを実行中にバックアップする場合は、通常のファイルとしてコピーするだけでは済みません。

オンザフライで安全にバックアップする必要がある場合は、最初に SQLite CLI を使用します。 .backup コマンドまたは SQLite オンライン バックアップ API。

sqlite3 app.db ".backup 'backup-$(date +%Y%m%d).db'"

トランザクションと同時実行性

SQLite はトランザクションをサポートします。複数の SQL ステートメントで作業単位を形成でき、途中で失敗した場合はロールバックできます。

BEGIN IMMEDIATE;

UPDATE accounts
SET balance = balance - 10000
WHERE id = 1;

UPDATE accounts
SET balance = balance + 10000
WHERE id = 2;

COMMIT;

同時実行性に関する最も重要な点は次のとおりです。複数の読み取りは同時に実行できますが、同じデータベース ファイルへの書き込みは基本的に同じライターを中心に調整されます。 WAL モードでは、読み取りと書き込みの間のブロッキングを軽減できますが、競合の多い書き込みが無制限に拡大することはできません。

WALモード

WAL (先行書き込みログ) は、最初に変更を別の WAL ファイルに記録します。公式ドキュメントには、WAL は多くのシナリオで高速であり、同様にリーダーがライターをブロックせず、ライターもリーダーをブロックしないことができるため、同時実行性が向上すると記載されています。

PRAGMA journal_mode = WAL;
PRAGMA synchronous = NORMAL;
PRAGMA busy_timeout = 5000;

これらの設定はアプリ開発では一般的ですが、すべての環境に対する固定された答えではありません。 WAL はメイン データベース ファイルの隣に生成される場合があります app.db-wal そして app.db-shm、ネットワーク ファイル システムまたは特殊なマルチプロセス展開では特に注意してください。

柔軟な型システムとSTRICTテーブル

SQLite は通常、PostgreSQL や MySQL のように列の種類によって値を強く制限しません。公式ドキュメントでは、SQLite を動的型システムとして説明しています。型は値自体に属します。たとえば、次のように宣言します INTEGER 特定の状況下では、列には他のタイプの値が格納される場合もあります。

この柔軟性は便利な場合もありますが、運用環境では「なぜ数値列に文字列があるの?」という問題が発生する可能性もあります。質問。より厳格なチェックが必要な場合は、SQLite 3.37.0 で導入されたチェックを検討してください。 STRICT 表面。

CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  age INTEGER CHECK (age >= 0),
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
) STRICT;

外部キーは接続ごとに有効化する

SQLite は外部キー制約をサポートしていますが、それが有効になっていると盲目的に想定しないでください。より安全な方法は、接続が作成されるたびにこれを行うことです。 PRAGMA foreign_keys = ON;

PRAGMA foreign_keys = ON;

CREATE TABLE authors (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

CREATE TABLE posts (
  id INTEGER PRIMARY KEY,
  author_id INTEGER NOT NULL,
  title TEXT NOT NULL,
  FOREIGN KEY (author_id) REFERENCES authors(id)
);

Node.js、Python、React Native、Android、ORM/ラッパー ライブラリのいずれを使用している場合でも、接続オプションまたは初期化フックを常に確認してください。この手順を行わないと、外部キーの動作がテーブル構造の表示と異なる場合があります。

直接実行できる SQLite サンプル

macOS と多くの Linux ディストリビューションにはすでに含まれています sqlite3 CLI、またはパッケージ マネージャー経由でインストールできます。

sqlite3 blog.db

まず単純なテーブルを作成し、データ行を挿入します。

PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;

CREATE TABLE IF NOT EXISTS notes (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
) STRICT;

INSERT INTO notes (title, body)
VALUES ('SQLite test', 'A local database example stored in one file without a server.');

SELECT id, title, created_at
FROM notes
ORDER BY id DESC;

シェルから一度実行することもできます。

sqlite3 blog.db <<'SQL'
PRAGMA foreign_keys = ON;
PRAGMA journal_mode = WAL;

CREATE TABLE IF NOT EXISTS notes (
  id INTEGER PRIMARY KEY,
  title TEXT NOT NULL,
  body TEXT NOT NULL,
  created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
) STRICT;

INSERT INTO notes (title, body)
VALUES ('SQLite test', 'A local database example stored in one file without a server.');

SELECT id, title, created_at
FROM notes
ORDER BY id DESC;
SQL

Python では、標準ライブラリの sqlite3 モジュールで小さな例を完成させることができます。

import sqlite3

with sqlite3.connect("blog.db") as conn:
    conn.execute("PRAGMA foreign_keys = ON")
    conn.execute("PRAGMA busy_timeout = 5000")
    conn.execute("""
        CREATE TABLE IF NOT EXISTS notes (
            id INTEGER PRIMARY KEY,
            title TEXT NOT NULL,
            body TEXT NOT NULL,
            created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
        ) STRICT
    """)
    conn.execute(
        "INSERT INTO notes (title, body) VALUES (?, ?)",
        ("SQLite test", "Use parameter binding instead of building SQL strings directly.")
    )
    rows = conn.execute("SELECT id, title FROM notes ORDER BY id DESC").fetchall()

print(rows)

ここでの焦点はパラメータのバインディングです。 SQLite がネイティブ データベースだからといって、SQL インジェクションのリスクが自動的になくなるわけではありません。ユーザー入力を SQL 文字列に直接連結しないでください。

実践的なベストプラクティス

1. 明示的な接続初期化SQL

SQLite には接続レベルで多くの設定があります。アプリの起動時や接続作成時に初期化SQLを固定の場所に置くことで、環境の違いによるバグを軽減できます。

PRAGMA foreign_keys = ON;
PRAGMA busy_timeout = 5000;
PRAGMA journal_mode = WAL;

知らせ:journal_mode データベースファイルに保存されますが、実際の戻り値は環境により異なる場合があります。セットアップが成功したと考えるだけでなく、返された結果を確認してください。

PRAGMA journal_mode;

2. 書き込みトランザクションを短くする

長い書き込みトランザクションでは、他の書き込みが待機したままになります。ファイルのダウンロード、ネットワーク呼び出し、画像処理などの遅い操作を書き込みトランザクションに含めないでください。トランザクションのスコープは、必要なデータベースの変更のみをカバーします。

3. 実際のクエリパターンに基づいてインデックスを作成する

SQLite ではインデックスの設計も必要です。頻繁に検索または並べ替えられる列にインデックスを付けて使用します EXPLAIN QUERY PLAN 実際に使用されるかどうかを確認します。

CREATE INDEX IF NOT EXISTS idx_notes_created_at
ON notes (created_at DESC);

EXPLAIN QUERY PLAN
SELECT id, title
FROM notes
WHERE created_at >= '2026-01-01'
ORDER BY created_at DESC;

4. 日付/時刻の保存形式をチームで先に決める

SQLite には専用の日付型がありません。一般的な選択肢には、ISO-8601 テキスト、Unix 時刻整数、またはユリウス日の数値が含まれます。アプリでタイム ゾーン ルールを一緒に定義しないと、並べ替え、比較、表示がすべて混乱する可能性があります。

多くのアプリ プロジェクトでは、UTC の ISO-8601 テキスト ストレージを使用し、表示するときにユーザーのタイム ゾーンに変換する方が理解しやすいです。

5. バックアップには SQLite ツールの使用を優先する

アプリがデータベースを使用している場合、ファイルを直接コピーすると、WAL ファイルまたはチェックポイントのステータスが失われる可能性があります。実行中のデータベース、優先度が与えられます .backup または SQLite オンライン バックアップ API。

sqlite3 app.db ".backup backup.db"
sqlite3 backup.db "PRAGMA integrity_check;"

よくある誤解や間違い

「SQLite はテスト用の単なるおもちゃのデータベースです」

いいえ。 SQLite は、ブラウザー、モバイル オペレーティング システム、アプリ、組み込みデバイス、その他のシナリオで広く使用されています。重要なのは目的です。ローカル、埋め込み、読み取り回数が多く書き込み回数が少ない、単一のファイル センターの問題に優れています。

「WAL を有効にすると、同時書き込みの問題はすべて解決されます。」

WAL は読み取りと書き込みの競合を減らすことができますが、同じデータベース ファイル上のすべての書き込み競合を排除することはできません。書き込み競合が多い場合は、書き込みキューを使用するか、トランザクションを短縮するか、中央サーバー データベースを使用するかどうかを再判断する必要があります。

「INTEGER PRIMARY KEY には常に AUTOINCREMENT が必要です」

SQLiteの公式ドキュメントでは次のように説明されています。AUTOINCREMENT 追加の CPU、メモリ、ディスク、および I/O オーバーヘッドが発生するため、厳密に必要な場合を除き、回避する必要があります。ほとんどの場合、INTEGER PRIMARY KEY それで十分です。これは、古い ROWID を再利用できないという明示的な要件がある場合にのみ考慮する必要があります。 AUTOINCREMENT

-- Most tables only need this.
CREATE TABLE items (
  id INTEGER PRIMARY KEY,
  name TEXT NOT NULL
);

-- Use only when old ROWIDs must not be reused.
CREATE TABLE audit_events (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  message TEXT NOT NULL
);

「外部キーが宣言されている場合は、自動的にチェックされます。」

外部キーの宣言と実行時チェックは個別に検討する必要があります。各接続を有効にする必要があります PRAGMA foreign_keys = ON;、無効なサブテーブル レコードが実際に拒否されるかどうかをテストします。

トラブル事例と解決策

ケース 1: 再発する database is locked または SQLITE_BUSY

症状:アプリは時々レポートします database is locked または SQLITE_BUSY。これは、複数のスレッドまたはプロセスが同時に書き込みを行っている場合に特によく発生します。

よくある理由:

  • 長い書き込みトランザクションによるロックの保持時間が長すぎます。
  • 接続が設定されていません busy_timeout、または時間が短すぎます。
  • 複数のライターが同じデータベース ファイルに同時にアクセスします。
  • ネットワーク呼び出しなどの遅い操作はトランザクション内に配置されます。
  • SQLite ファイルはネットワーク ファイル システム上に配置されます。

最初のチェック:

PRAGMA journal_mode;
PRAGMA busy_timeout;
# macOS/Linux example: check which process is holding the DB file
lsof app.db

解決策の方向性:

PRAGMA busy_timeout = 5000;
PRAGMA journal_mode = WAL;
  • 書き込みトランザクションを短縮します。
  • 書き込みキューを使用して、アプリ内で書き込み操作をシリアル化します。
  • SQLite データベース ファイルをネットワーク共有に配置しないでください。
  • 複数のサーバーが同じデータベースを共有する必要がある場合は、PostgreSQL または MySQL を検討する必要があります。

ケース 2: 外部キーが宣言されているが、依然として間違ったデータが挿入されている

症状:内外で宣言 FOREIGN KEYただし、親テーブルのレコードのない子テーブルのデータは引き続き正常に挿入されます。

理由:SQLite 接続では、外部キーのチェックがオンになっていない可能性があります。公式ドキュメントによると、外部キーは実行時に渡す必要があります。 PRAGMA foreign_keys = ON; 有効になり、接続によって有効になります。

診る:

PRAGMA foreign_keys;
PRAGMA foreign_key_check;

解決する:

PRAGMA foreign_keys = ON;

接続が作成されるたびに、初期化プロセス中に実行されます。 ORM を使用している場合は、SQLite 接続オプションまたはイベント フックを確認してください。

ケース 3: 数値列と文字列が混在し、ソート結果がおかしい

症状:として宣言される INTEGER 列に文字列値が表示され、並べ替えまたは比較の結果が期待どおりになりません。

理由:SQLite のデフォルトの型システムはより柔軟です。列宣言だけでは、すべての型の不一致を強制的に防ぐことはできません。

診る:

SELECT id, age, typeof(age)
FROM users
WHERE typeof(age) <> 'integer';

解決策の方向性:

  • 新しいテーブルまたは移行されたテーブルの使用を検討してください STRICT
  • アプリ入力層に検証を追加します。
  • 移行前に使用 typeof() 既存のデータを確認します。
CREATE TABLE users_new (
  id INTEGER PRIMARY KEY,
  email TEXT NOT NULL UNIQUE,
  age INTEGER CHECK (age >= 0)
) STRICT;

ケース 4: バックアップのコピー後に最近のデータ損失が発見された

症状:アプリの実行中は、 .db ファイル、最近書き込まれたデータは回復後に見ることができません。

理由:WAL モードでは、最近の変更がまだ残っている可能性があります。 -wal ファイル内にあります。使用中のマスター データベース ファイルのみがコピーされるため、一貫したバックアップが取得できない場合があります。

解決する:

sqlite3 app.db ".backup backup.db"
sqlite3 backup.db "PRAGMA integrity_check;"

アプリで SQLite Online Backup API を使用することもできます。ファイルを直接コピーする必要がある場合は、まずデータベース接続を閉じて、チェックポイントのステータスを確認してください。

結論

SQLite は、「ファイル内の軽量データベース」よりもはるかに実用的です。データをアプリの近くに置く必要があるローカル アプリ、モバイル アプリ、組み込みデバイス、テスト環境、ガジェットなどのシナリオで、インストール、運用、メンテナンスのコストを大幅に削減できます。

ただし、複数のサーバーが同時に中央データベースに大量の書き込みを行う場合は、SQLite に間違った役割を強制しないでください。現時点ではPostgreSQLかMySQLを選択した方が無難です。 SQLite を有効に活用するための鍵は、できるだけ早くその役割を明確にし、PRAGMA を均一に初期化し、ショート ライト トランザクションを維持し、WAL を理解し、外部キーを有効にし、正しいバックアップ方法を計画することです。

参考文献

韓国語原文:この記事は韓国語原文をもとに、日本語読者向けに用語、確認手順、注意点を整理しています。韓国語原文を見る