目次
「新しいプロジェクトのデータベースをMongoDBにするかPostgreSQLにするか迷っている」「NoSQLとRDBの違いがいまいちわからない」「それぞれどんな場面で使うべき?」――データベース選定は、プロジェクトの成功を左右する重要な意思決定です。
本記事では、ドキュメント型NoSQLの代表格MongoDBと、リレーショナルデータベースの代表格PostgreSQLを、データモデル、クエリ、パフォーマンス、スケーラビリティなどの観点からコード例を交えて徹底比較します。プロジェクトの要件に応じた最適な選択ができるようになるでしょう。
データモデルの根本的な違い
MongoDBとPostgreSQLの最大の違いは、データモデルのアプローチです。
PostgreSQL:リレーショナルモデル
PostgreSQLはテーブル(行と列)でデータを管理し、テーブル間をリレーション(外部キー)で結びつけます。
-- PostgreSQL:正規化されたテーブル設計
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(255) UNIQUE NOT NULL
);
CREATE TABLE orders (
id SERIAL PRIMARY KEY,
user_id INTEGER NOT NULL REFERENCES users(id),
total NUMERIC(10, 2) NOT NULL,
status VARCHAR(20) DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE order_items (
id SERIAL PRIMARY KEY,
order_id INTEGER NOT NULL REFERENCES orders(id),
product_name VARCHAR(255) NOT NULL,
quantity INTEGER NOT NULL,
unit_price NUMERIC(10, 2) NOT NULL
);
-- データの取得:JOINで結合
SELECT
u.name,
o.id AS order_id,
o.total,
oi.product_name,
oi.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
WHERE u.id = 1;
MongoDB:ドキュメントモデル
MongoDBはJSON/BSONドキュメントでデータを管理します。関連するデータをネスト(埋め込み)して1つのドキュメントに収められます。
// MongoDB:ドキュメント設計
db.orders.insertOne({
user: {
name: "田中太郎",
email: "tanaka@example.com"
},
items: [
{
productName: "ノートPC",
quantity: 1,
unitPrice: 89800
},
{
productName: "マウス",
quantity: 2,
unitPrice: 3980
}
],
total: 97760,
status: "pending",
createdAt: new Date()
});
// データの取得:JOINなしで全情報が取得できる
db.orders.findOne({ "user.email": "tanaka@example.com" });
スキーマの柔軟性
PostgreSQLはスキーマオンライトで、データを書き込む前にテーブル構造を定義する必要があります。一方、MongoDBはスキーマレス(正確にはスキーマオンリード)で、同じコレクション内でもドキュメントごとに異なる構造を持てます。
// MongoDBでは同じコレクションに異なる構造のドキュメントが入る
db.products.insertMany([
{
name: "ノートPC",
category: "電子機器",
specs: { cpu: "M3", memory: "16GB", storage: "512GB" }
},
{
name: "Tシャツ",
category: "衣類",
sizes: ["S", "M", "L", "XL"],
colors: ["白", "黒", "ネイビー"]
}
]);
ただし、実際のプロジェクトではMongoDBでもバリデーションスキーマを使って構造を制約することが推奨されます。
// MongoDBのスキーマバリデーション
db.createCollection("users", {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name", "email"],
properties: {
name: { bsonType: "string", minLength: 1 },
email: { bsonType: "string", pattern: "^.+@.+\\..+$" },
age: { bsonType: "int", minimum: 0 }
}
}
}
});
クエリ言語の比較
両者のクエリ方法を、同じ操作で比較してみましょう。
基本的なCRUD操作
-- PostgreSQL:INSERT
INSERT INTO users (name, email) VALUES ('田中太郎', 'tanaka@example.com');
-- PostgreSQL:SELECT
SELECT * FROM users WHERE email = 'tanaka@example.com';
-- PostgreSQL:UPDATE
UPDATE users SET name = '田中一郎' WHERE email = 'tanaka@example.com';
-- PostgreSQL:DELETE
DELETE FROM users WHERE email = 'tanaka@example.com';
// MongoDB:insert
db.users.insertOne({ name: "田中太郎", email: "tanaka@example.com" });
// MongoDB:find
db.users.findOne({ email: "tanaka@example.com" });
// MongoDB:update
db.users.updateOne(
{ email: "tanaka@example.com" },
{ $set: { name: "田中一郎" } }
);
// MongoDB:delete
db.users.deleteOne({ email: "tanaka@example.com" });
集計処理の比較
-- PostgreSQL:カテゴリ別の売上合計
SELECT
category,
COUNT(*) AS order_count,
SUM(total) AS total_revenue,
AVG(total) AS avg_order_value
FROM orders
WHERE created_at >= '2026-01-01'
GROUP BY category
HAVING SUM(total) > 100000
ORDER BY total_revenue DESC;
// MongoDB:Aggregation Pipeline
db.orders.aggregate([
{ $match: { createdAt: { $gte: ISODate("2026-01-01") } } },
{ $group: {
_id: "$category",
orderCount: { $sum: 1 },
totalRevenue: { $sum: "$total" },
avgOrderValue: { $avg: "$total" }
}
},
{ $match: { totalRevenue: { $gt: 100000 } } },
{ $sort: { totalRevenue: -1 } }
]);
PostgreSQLのSQLは宣言的で読みやすく、SQLの知識がある開発者にとって直感的です。MongoDBのAggregation Pipelineはパイプライン(ステージの連鎖)として表現され、段階的にデータを変換するアプローチです。
フルテキスト検索の比較
-- PostgreSQL:全文検索
-- 日本語対応にはpg_bigmやpg_trgm拡張が必要
CREATE INDEX idx_posts_search ON posts USING GIN (to_tsvector('english', title || ' ' || content));
SELECT title, ts_rank(to_tsvector('english', title || ' ' || content),
to_tsquery('english', 'database & performance')) AS rank
FROM posts
WHERE to_tsvector('english', title || ' ' || content)
@@ to_tsquery('english', 'database & performance')
ORDER BY rank DESC;
// MongoDB:テキストインデックスによる全文検索
db.posts.createIndex({ title: "text", content: "text" });
db.posts.find(
{ $text: { $search: "database performance" } },
{ score: { $meta: "textScore" } }
).sort({ score: { $meta: "textScore" } });
// MongoDB Atlas Searchを使えばより高度な検索が可能
パフォーマンスとスケーラビリティ
読み書きパフォーマンス
MongoDBが優位なケース
- 単一ドキュメントの読み書き:JOINなしで関連データを一度に取得できるため高速
- 書き込み負荷が高い場合:シャーディングによる水平スケーリングが容易
- スキーマの頻繁な変更:ALTER TABLEのような重い操作が不要
PostgreSQLが優位なケース
- 複雑なJOINクエリ:洗練されたクエリオプティマイザによる効率的な実行
- トランザクション処理:複数テーブルにまたがるACIDトランザクション
- 集計・分析:ウィンドウ関数やCTEによる高度な分析クエリ
スケーリングアプローチ
MongoDB:水平スケーリング(シャーディング)
// MongoDBのシャーディング設定例
sh.enableSharding("mydb");
sh.shardCollection("mydb.orders", { user_id: "hashed" });
// シャードキーの選択が重要:
// - user_id でシャーディング → ユーザーごとのクエリが高速
// - created_at でシャーディング → 時系列データに最適
MongoDBは設計段階からシャーディング(データの水平分割)を前提としており、大量のデータと高いスループットに対応できます。
PostgreSQL:垂直スケーリング + リードレプリカ
# PostgreSQLのリードレプリカ構成
# プライマリサーバー(書き込み用)
# ├── リードレプリカ1(読み取り専用)
# ├── リードレプリカ2(読み取り専用)
# └── リードレプリカ3(読み取り専用)
# テーブルパーティショニングによるスケーリング
CREATE TABLE access_logs (
id BIGSERIAL,
created_at TIMESTAMPTZ NOT NULL
) PARTITION BY RANGE (created_at);
PostgreSQLは基本的に垂直スケーリング(サーバースペックの強化)とリードレプリカで対応しますが、Citus拡張を使えば水平スケーリングも可能です。
アプリケーションコードでの使い方比較
Node.js/TypeScriptでの実装例を比較します。
PostgreSQL(Prisma)
// schema.prisma
model User {
id Int @id @default(autoincrement())
name String
email String @unique
orders Order[]
}
model Order {
id Int @id @default(autoincrement())
user User @relation(fields: [userId], references: [id])
userId Int
total Decimal
status String @default("pending")
items OrderItem[]
createdAt DateTime @default(now())
}
model OrderItem {
id Int @id @default(autoincrement())
order Order @relation(fields: [orderId], references: [id])
orderId Int
productName String
quantity Int
unitPrice Decimal
}
// PostgreSQL + Prisma でのクエリ
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
// ユーザーの注文履歴を取得(リレーション付き)
const userWithOrders = await prisma.user.findUnique({
where: { email: 'tanaka@example.com' },
include: {
orders: {
include: { items: true },
orderBy: { createdAt: 'desc' },
take: 10,
},
},
});
// 売上集計
const salesByMonth = await prisma.$queryRaw`
SELECT
DATE_TRUNC('month', created_at) AS month,
COUNT(*) AS order_count,
SUM(total) AS revenue
FROM orders
WHERE status = 'completed'
GROUP BY month
ORDER BY month DESC
`;
MongoDB(Mongoose)
// MongoDB + Mongoose のスキーマ定義
import mongoose from 'mongoose';
const orderSchema = new mongoose.Schema({
user: {
name: { type: String, required: true },
email: { type: String, required: true },
},
items: [{
productName: { type: String, required: true },
quantity: { type: Number, required: true, min: 1 },
unitPrice: { type: Number, required: true },
}],
total: { type: Number, required: true },
status: {
type: String,
enum: ['pending', 'paid', 'shipped', 'delivered', 'cancelled'],
default: 'pending',
},
createdAt: { type: Date, default: Date.now },
});
// インデックスの作成
orderSchema.index({ 'user.email': 1, createdAt: -1 });
orderSchema.index({ status: 1 });
const Order = mongoose.model('Order', orderSchema);
// MongoDB + Mongoose でのクエリ
// ユーザーの注文履歴を取得
const orders = await Order.find({ 'user.email': 'tanaka@example.com' })
.sort({ createdAt: -1 })
.limit(10);
// 売上集計
const salesByMonth = await Order.aggregate([
{ $match: { status: 'completed' } },
{ $group: {
_id: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
},
orderCount: { $sum: 1 },
revenue: { $sum: '$total' },
},
},
{ $sort: { '_id.year': -1, '_id.month': -1 } },
]);
ユースケース別の選定ガイド
PostgreSQLを選ぶべきケース
1. ECサイト・業務システム:在庫管理、注文処理、会計処理など、データの整合性が重要なシステムではACIDトランザクションが必須です。
2. 複雑なレポーティング:売上分析、KPIダッシュボードなど、複雑なJOINや集計が多いケースではSQLの表現力が活きます。
3. 地理空間データ:PostGIS拡張を使えば、位置情報の検索や距離計算が効率的に行えます。
4. 既存のSQLスキル:チームにSQL経験者が多い場合、学習コストが低くなります。
MongoDBを選ぶべきケース
1. コンテンツ管理システム(CMS):記事やページの構造が柔軟に変化するCMSでは、スキーマレスの利点が活きます。
2. IoT・ログデータ:大量のセンサーデータやアクセスログなど、書き込みが多く構造が変わりやすいデータに適しています。
3. プロトタイピング:スキーマ定義なしでデータを投入できるため、初期段階の素早い開発に向いています。
4. 大規模な水平スケーリング:数TB以上のデータを複数サーバーに分散する必要がある場合、MongoDBのシャーディングが有利です。
両方を併用するケース
実際のプロジェクトでは、両方を併用するケースもあります。
# マイクロサービスアーキテクチャでの併用例
# ├── ユーザーサービス:PostgreSQL(認証、プロフィール、権限管理)
# ├── 注文サービス:PostgreSQL(注文処理、在庫管理、決済)
# ├── 商品カタログサービス:MongoDB(商品情報、カテゴリ別の属性)
# ├── ログ・分析サービス:MongoDB(アクセスログ、行動データ)
# └── 検索サービス:Elasticsearch(全文検索)
PostgreSQLのJSONBで「いいとこ取り」
実は、PostgreSQLにはJSONB型があり、ドキュメント的なデータモデルも扱えます。
JSONBを使ったハイブリッド設計
-- リレーショナル + ドキュメントのハイブリッド
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
price NUMERIC(10, 2) NOT NULL,
category VARCHAR(100) NOT NULL,
attributes JSONB DEFAULT '{}' -- カテゴリ別の可変属性
);
-- カテゴリごとに異なる属性をJSONBで保存
INSERT INTO products (name, price, category, attributes) VALUES
('MacBook Pro', 248800, 'ノートPC',
'{"cpu": "M3 Pro", "memory": "18GB", "storage": "512GB", "display": "14.2インチ"}'),
('Tシャツ', 3980, '衣類',
'{"size": "L", "color": "ネイビー", "material": "コットン100%"}');
-- JSONBの検索
SELECT name, price, attributes->>'cpu' AS cpu
FROM products
WHERE category = 'ノートPC'
AND (attributes->>'memory')::int >= 16;
-- GINインデックスで高速検索
CREATE INDEX idx_products_attributes ON products USING GIN (attributes);
PostgreSQLのJSONB型は、リレーショナルモデルの堅牢さとドキュメントモデルの柔軟性を1つのデータベースで実現できる強力な機能です。多くのケースで、MongoDBを使わずにPostgreSQLだけで対応できます。
まとめ:選定の判断基準
MongoDBとPostgreSQLの選択は、プロジェクトの要件によって決まります。
PostgreSQLを選ぶ判断基準
- データの整合性・一貫性が最重要
- 複雑なリレーションを持つデータ構造
- JOINや複雑な集計クエリが多い
- ACIDトランザクションが必須
- チームにSQLのスキルがある
MongoDBを選ぶ判断基準
- データ構造が頻繁に変わる
- ネストされたドキュメントが多い
- 書き込み負荷が非常に高い
- 大規模な水平スケーリングが必要
- 素早いプロトタイピングが優先
迷った場合は、まずPostgreSQLを選択し、JSONBで柔軟性が必要な部分に対応するアプローチがおすすめです。PostgreSQLのJSONBは非常に高機能で、多くのユースケースをカバーできます。MongoDBの導入は、明確にMongoDBが適しているユースケース(大量ログデータ、IoTデータなど)に限定するとよいでしょう。
Harmonic Society
この記事の内容、自社の業務でも活かせそうですか?
ローカルLLM・AI・クラウドなどの技術導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK。まずは30分の無料相談から。売り込みはしません。
関連記事
Related / 9 articles
Notes & Insights
- プログラミング
DDoS攻撃の仕組みと対策入門|レイヤー別の防御とCDN・クラウドの活用
DDoS攻撃をボリューム型・プロトコル型・アプリ層に分けて仕組みを解説し、自前サーバーで防げない理由、CloudflareやAWS Shieldの標準防御、オリジンIPの隠し方、レートリミットとBot対策、攻撃を受けたときの初動、費用が跳ね上がるDenial of Walletへの備えまでわかります。
- プログラミング
WAFとは?仕組み・導入パターン・誤検知対策|Webアプリを攻撃から守る実践ガイド
WAFがファイアウォールやIDSと何が違うのか、シグネチャとマネージドルールの仕組み、Cloudflare WAF・AWS WAF・ModSecurityの比較、フォーム送信がブロックされる誤検知の調査と例外設定、ログ監視、WAFが代替できないことまで実践的に解説します。
- プログラミング
セキュリティヘッダー入門|CSP・HSTS・X-Frame-Optionsの設定と効果を実践解説
CSP・HSTS・X-Frame-Options・X-Content-Type-Optionsなど主要セキュリティヘッダーが防ぐ攻撃と、CSPのReport-Onlyからの段階導入、nonce/hash、HSTS preloadの不可逆リスク、Nginx・Next.js・Astroでの設定例、確認方法を解説します。
- プログラミング
クラウドの通信費(Egress)入門|データ転送量課金の仕組みと転送コストを抑える設計
クラウドの「受信無料・送信有料」の原則、AZ間・リージョン間・インターネット向けの単価差、NATゲートウェイ処理料の罠、CDNで転送量を減らす方法、バックアップやログ転送の見落とし、請求書で転送料を特定する手順を解説。想定外の請求を防げます。
- プログラミング
秘密情報をGitに入れない仕組み|.gitignore・git-secrets・履歴から漏れた鍵の削除
APIキーや.envをGitにコミットしてしまう典型経路と、.gitignore・.env.exampleの運用、pre-commitでのgitleaks検知、GitHub secret scanningの活用、漏れた鍵の無効化と履歴書き換え(git filter-repo)の手順を解説。仕組みで再発を防げます。
- プログラミング
開発・ステージング・本番環境の分離設計|環境差分をなくす構成とアクセス制御
開発・ステージング・本番それぞれの目的と、構成をコードで揃える方法、環境別の設定注入、本番データを使わないテストデータ戦略、ステージングの保護(Basic認証・IP制限・noindex)、コストを抑える運用までを解説。環境差分による本番障害を防げます。
- プログラミング
ngrok・Cloudflare Tunnelでローカルを公開|Webhook開発とデモ環境の作り方
NAT内のローカル環境にStripeやLINEのWebhookを届けるトンネリングの仕組みを解説。ngrok・Cloudflare Tunnel・localtunnelの比較、固定ドメインと認証、リクエスト検査、公開時のセキュリティ、自宅サーバー公開への応用までわかります。
- プログラミング
ローカル開発環境のHTTPS化|mkcert・hostsファイル・自己署名証明書の正しい使い方
ローカル開発をHTTPS前提にすべき理由(Secure Cookie・Service Worker・OAuth)と、mkcertでローカルCAを作りhostsで独自ドメインを割り当ててVite・Next.js・Dockerで使う手順を解説。証明書警告を無視する癖の危険も理解できます。
- プログラミング
localhost・0.0.0.0・127.0.0.1の違い|ポートとUnixソケットを理解して「つながらない」を解決
localhost・127.0.0.1・0.0.0.0の意味の違い、Dockerで外から接続できない原因、host.docker.internal、ポート競合の調べ方、Unixソケットの利点と権限、1024未満ポートの制約を解説。「つながらない」を仕組みから解決できます。
Harmonic Society
「読んで終わり」にせず、自社の業務で試してみませんか?
AI・ローカルLLM・クラウドの導入を、要件整理からPoC・社内展開まで代表エンジニアが伴走します。オンライン対応・全国OK・売り込みなし。
無料・30分・オンラインOK|1営業日以内に返信します