2つのデータベース、まったく違う設計思想
SQLite と PostgreSQL は、どちらも SQL を話せて、リレーショナルデータを扱えて、本番アプリケーションも動かせます。ただ、共通点はそこまで。両者は前提としている世界がまるで違います。
- SQLite はライブラリです。アプリケーションのプロセス内で動き、ディスク上の 1 つの
.dbファイルを読み書きします。サーバーも不要、ポートも不要、ユーザー設定もありません。 - PostgreSQL はサーバーです。独立したプロセスとして動き、ネットワークポートで待ち受け、アプリケーションはクライアントとして接続しに行きます。
両者の違い —— 同時実行性、デプロイ方法、型の厳格さ、パフォーマンス —— は、ほぼすべてこの「アーキテクチャの違い」から派生しています。これを念頭に置いて読み進めてください。
アーキテクチャ:インプロセス vs クライアント/サーバー
SQLite でデータベースを開くのは、ファイルを開くのと同じです:
起動するデーモンもなければ、編集する pg_hba.conf も、開けておくポートもありません。アプリが SQLite ライブラリを読み込んで notes.db を開けば、もうクエリを投げられる状態になっています。デプロイは「ファイルをコピーするだけ」です。
一方、PostgreSQL はこんな感じになります。
# サーバーを起動する(管理者として一度だけ実行):
sudo systemctl start postgresql
# その後、アプリから接続する:
psql -h localhost -U alice -d mydb
アプリケーションは別プロセスと通信することになります。多くの場合は TCP 経由、ときには Unix ソケット経由です。この一層が増える分、セットアップに手間がかかり、クエリごとに接続のラウンドトリップも発生します。その代わりに、ネットワーク越しのアクセス、マルチユーザー認証、そして真の同時書き込みが手に入ります。
同時書き込みこそが最大の論点
SQLite と PostgreSQL を使い分けるとき、たいていここが決め手になります。SQLite は書き込みを直列化する仕組みで、ある瞬間に書き込みできるのは一人だけ。データベースファイルにロックをかけたライターがいる間、他のライターは順番待ちです。読み込みは並列に走れます(特に WAL モードなら顕著)が、書き込みは一件ずつ処理されます。
一方の PostgreSQL は MVCC(マルチバージョン同時実行制御)と行レベルロックを採用しているので、複数のトランザクションが別々の行に同時に書き込んでも互いをブロックしません。
実際の使い分けはこんな感じです。
- 読者が秒間 50 人いて、たまに著者が記事を投稿するブログ? SQLite で十分です。
- 何百人ものユーザーが同時に在庫を更新する EC のチェックアウト? PostgreSQL 一択。
- モバイルアプリのローカルキャッシュ? 文句なしで SQLite。
- バックグラウンドワーカーが何十も走るマルチテナント型 SaaS のバックエンド? PostgreSQL。
WAL モード(PRAGMA journal_mode = WAL;)を有効にすれば SQLite の同時実行性は大きく改善し、読み込みが書き込みをブロックしなくなります。ただし「ライターは同時に一人だけ」というルール自体は変わりません。
型システム:緩いか、厳しいか
PostgreSQL は厳格です。INTEGER 型として宣言したカラムに文字列を入れようとすると、問答無用で拒否されます。
-- Postgres
CREATE TABLE t (n INTEGER);
INSERT INTO t (n) VALUES ('not a number');
-- エラー: integer 型の入力構文が無効です
SQLiteは標準で 型親和性(type affinity) を採用していて、これは厳密なルールというより「こういう型を入れてほしい」という"提案"のようなものです。なので、次のようなINSERTもエラーにならずそのまま通ってしまいます。
文字列はそのまま INTEGER カラムに収まってしまい、SQLite はそれをテキストとして保存します。この柔軟さは意図的な設計判断で、サクッとプロトタイプを作るには便利ですが、長く運用するスキーマでは危険な落とし穴になります。
新しめの SQLite(3.37 以降)では、PostgreSQL に近い挙動をする STRICT テーブルが使えるようになっています。
新しく SQLite プロジェクトを始めるなら、STRICT を使っておきましょう。「数値カラムになぜか文字列が入ってる」みたいなトラブルをまとめて防げます。
機能の幅
ほぼあらゆる面で PostgreSQL の方が機能が豊富です。データ型(配列、範囲型、幾何型、ネットワーク型、カスタム enum)、手続き型言語(PL/pgSQL、PL/Python)、ランキング付き全文検索、マテリアライズドビュー、テーブルパーティショニング、レプリケーション、ロールベースのセキュリティ、そして充実した拡張エコシステム(PostGIS、TimescaleDB、pgvector)など、挙げればきりがありません。
一方の SQLite は基本機能に加えて、その規模感に見合った便利機能をいくつか備えています。JSON 関数、FTS5 による全文検索、R-Tree インデックス、ウィンドウ関数、CTE、生成カラムなどです。逆に省かれているのは、サーバーを前提とする機能、つまりユーザー、ロール、レプリケーション、ネットワークアクセスなど。
ざっくりした使い分けの目安はこんな感じです:
- GIS、ベクトル検索、レプリケーションが必要 → PostgreSQL
- iOS アプリの中にデータベースを同梱したい → SQLite
- 両方使いたい → 開発・テストは SQLite、本番は PostgreSQL という構成にしているチームも多いです。ただし後述するように、構文の違いで痛い目を見ることがあります。
SQLite と PostgreSQL の構文の違い
普段書く SQL のほとんどは共通です。違いが出てくるのはスキーマ定義、データ型、そして一部の組み込み関数まわりに集中しています:
-- 自動採番される主キー
-- SQLite:
CREATE TABLE users (id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT);
-- Postgres:
CREATE TABLE users (id SERIAL PRIMARY KEY, name TEXT);
-- または、モダンな Postgres の場合:
CREATE TABLE users (id INTEGER GENERATED ALWAYS AS IDENTITY PRIMARY KEY, name TEXT);
-- 現在のタイムスタンプ
-- SQLite: CURRENT_TIMESTAMP (テキストを返す)
-- Postgres: NOW() (タイムスタンプを返す)
-- 真偽値型
-- SQLite: 本物の BOOLEAN はない。INTEGER の 0/1 を使う
-- Postgres: TRUE/FALSE を持つ BOOLEAN 型
SQLiteで開発してPostgresにデプロイする運用なら、生のSQLを直接書かずにORMかマイグレーションツールを挟んでおくのが無難です。そうしないと、こうした方言の違いがアプリ側にじわじわ染み出してきます。
SQLite と PostgreSQL の性能比較を正直に
「どっちが速いか」は、何をやらせるかで答えが変わります。1プロセスで読み込みと小さめの書き込みをこなすだけなら、SQLiteはなかなか手強い相手です。ネットワーク越しの通信もなければ、プロトコルのパースも、クライアント接続の確立もないからです。クライアントが1つだけのベンチマークでは、シンプルなクエリならSQLiteの方がPostgresより速いことも珍しくありません。
ところが、同時書き込みが増えたり、並列クエリ実行が必要な大規模データを扱ったり、Postgresの成熟したプランナの恩恵を受けるような複雑なクエリプランが絡んでくると、形勢は一気にPostgres有利になります。Postgresは縦方向にスケール(マシンを大きく、コアを増やす)できる設計ですが、SQLiteはそもそもそういう用途を想定していません。
正直なところをまとめると、SQLiteは「SQLiteが得意な領域」では速く、Postgresは「Postgresが得意な領域」では速い。ベンチマークの見出しではなく、自分のワークロードの形で選ぶべきです。
SQLite と PostgreSQL の使い分けガイド
次のような場合は SQLite を選びます。
- データが1つのアプリと一緒に置かれる場合(デスクトップ、モバイル、組み込み、CLIツールなど)。
- 書き込みが単一プロセス、または少数のプロセスに限られる場合。
- セットアップ不要でデプロイしたい場合。
- プロトタイピング中で、インフラよりスキーマ設計に集中したい場合。
一方、次のような場合は Postgres を選びます。
- 複数のアプリケーションサーバーやワーカーが同じDBに書き込む場合。
- ネットワーク経由で多くのクライアントからアクセスする必要がある場合。
- ロール管理、レプリケーション、GIS、カスタム型、ストアドプロシージャといった高度な機能が必要な場合。
- 本番サービスの中核データストアとして、永続的に使う場合。
よくある進め方として、小さなプロジェクトはSQLiteで始め、トラフィックの形が変わってきた段階でPostgresに移行する、というパターンがあります。SQLiteからPostgresへの移行はタダではありませんが、定石のある作業ですし、そもそも多くのプロジェクトでは移行が必要になることはありません。
次回:SQLite が本当に最適な場面
ここまでで両者のトレードオフは見えてきました。次のページでは、SQLiteを積極的に選ぶ理由をもう一歩踏み込んで解説します。「とりあえず使える」レベルではなく、本当にSQLiteの方が優れた選択肢になるワークロードと、逆に「そろそろSQLiteを卒業すべきサイン」について見ていきましょう。
よくある質問
SQLiteとPostgreSQLの一番大きな違いは?
SQLiteはアプリのプロセス内で動作し、1つのファイルを直接読み書きする「組み込み型」のライブラリです。一方のPostgreSQLは独立したサーバープロセスで、ネットワーク経由で接続して使います。この構造の違いが、並行処理・デプロイ方法・型システム・周辺ツールといった他のすべての差につながっていると言っても過言ではありません。
SQLiteはPostgreSQLより速い?
シングルプロセスでの読み込みや小規模な書き込みなら、SQLiteの方が速いことが多いです。ネットワークの往復もクライアント/サーバー間のプロトコルオーバーヘッドもないからですね。ただし、複数クライアントから同時に書き込みが走るようなケースでは、行レベルロックとMVCCを備えたPostgreSQLが優勢になります。「どちらが速いか」はエンジン単体ではなく、ワークロードの形によって決まります。
SQLiteを本番環境で使っても大丈夫?
ワークロードの性質が合えば、まったく問題ありません。Webサイト、デスクトップアプリ、組み込み機器など、本番でSQLiteを採用している例はたくさんあります。判断の境目は「同時に書き込むプロセスがどれくらいあるか」です。多数のプロセスから同時書き込みが必要ならPostgreSQLが向いています。SQLiteは書き込みを直列化する仕組みなので、WALモードで多少緩和できても上限があると考えてください。
SQLiteからPostgreSQLへの移行はどうやる?
まず sqlite3 mydb.db .dump でスキーマとデータをエクスポートし、SQLを書き換えます。AUTOINCREMENT は SERIAL または GENERATED AS IDENTITY に置き換える必要がありますし、型名の調整、SQLiteの緩い型付けに依存している箇所の修正も必要です。pgloader のようなツールを使えば大半は自動化できますが、SQLite特有の柔軟な型に頼ったコードは書き直す前提で計画しておくのが安全です。