結論先講
2026 年,除非你有非常明確的理由,否則預設選 PostgreSQL。 MongoDB 適合的場景比大多數人想的要窄——CMS 內容管理、IoT 事件日誌、快速原型。如果你的資料有任何「關聯」需求(使用者有訂單、訂單有商品),關聯式資料庫永遠是更安全的選擇。
一個殘酷的統計:在我見過的 MongoDB 專案中,超過一半最終都遷回了 PostgreSQL 或 MySQL。而反過來的案例,幾乎沒有。
MongoDB 為什麼那麼紅?
2010 年代初期,MongoDB 的行銷策略非常成功。它打中了開發者幾個痛點:
- 不用寫 schema——丟 JSON 進去就好,改格式不用跑 migration
- JavaScript 全棧——Node.js + Express + MongoDB(MEAN/MERN stack),前後端都是 JSON
- 水平擴展容易——內建 sharding,「web scale」的口號很吸引人
- 入門超簡單——
db.users.insertOne({name: "Terry"})就存進去了
// MongoDB 的魅力:存什麼都行
db.products.insertOne({
name: "筆電",
price: 35000,
specs: {
cpu: "M4 Pro",
ram: "36GB",
storage: "1TB SSD"
},
tags: ["apple", "laptop", "2025"],
reviews: [
{ user: "Alice", rating: 5, comment: "超讚" },
{ user: "Bob", rating: 4, comment: "不錯" }
]
});看起來很美好,對吧?問題在後面。
MongoDB 的坑
1. 「Schema-less 是個謊言」
MongoDB 號稱 schema-less,但你的應用程式一定有 schema。差別只是這個 schema 從資料庫層搬到了應用層。
// 同一個 collection 裡可能有這些混亂的資料
{ name: "Alice", age: 25 }
{ name: "Bob", age: "thirty" } // age 變字串了
{ Name: "Charlie", AGE: 25 } // 欄位名大小寫不同
{ name: "Dave" } // 少了 age
{ name: "Eve", age: 25, email: null } // 多了 email沒有 schema 不代表不需要 schema,只代表沒有人幫你檢查。 三個月後你的 collection 就是一團亂,而且 MongoDB 不會告訴你。
2. 沒有真正的 JOIN
MongoDB 有 $lookup(3.2+ 版本加的),但它跟 SQL 的 JOIN 比起來:
// MongoDB $lookup(模擬 JOIN)
db.orders.aggregate([
{
$lookup: {
from: "users",
localField: "userId",
foreignField: "_id",
as: "user"
}
},
{ $unwind: "$user" },
{
$lookup: {
from: "products",
localField: "items.productId",
foreignField: "_id",
as: "products"
}
}
]);-- PostgreSQL JOIN(清楚多了)
SELECT o.*, u.name, p.title
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON oi.order_id = o.id
JOIN products p ON p.id = oi.product_id;$lookup 在跨 shard 的情況下效能更差,而且不支援索引優化。
3. 交易支援來得太晚
MongoDB 4.0(2018 年)才支援多文件交易,4.2 才支援分散式交易。這代表在此之前,「扣庫存 + 建立訂單」這種基本操作都沒辦法保證原子性。
// MongoDB 交易(4.0+)
const session = client.startSession();
try {
session.startTransaction();
await db.inventory.updateOne(
{ sku: "abc123", qty: { $gte: 1 } },
{ $inc: { qty: -1 } },
{ session }
);
await db.orders.insertOne(
{ sku: "abc123", userId: "user1" },
{ session }
);
await session.commitTransaction();
} catch (e) {
await session.abortTransaction();
} finally {
session.endSession();
}能用,但效能比單文件操作慢很多,而且 MongoDB 官方自己都說「如果你需要交易,考慮把相關資料放在同一個文件」。
PostgreSQL JSONB:兩個世界的交集
PostgreSQL 的 JSONB 欄位類型讓你在關聯式資料庫裡享受 Document DB 的彈性:
CREATE TABLE products (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
price DECIMAL(10,2) NOT NULL,
-- 結構化欄位放外面,彈性部分用 JSONB
specs JSONB,
metadata JSONB
);
-- 插入
INSERT INTO products (name, price, specs, metadata)
VALUES (
'筆電',
35000,
'{"cpu": "M4 Pro", "ram": "36GB", "storage": "1TB SSD"}',
'{"tags": ["apple", "laptop"], "reviews_count": 42}'
);
-- 查詢 JSON 欄位
SELECT name, specs->>'cpu' AS cpu, price
FROM products
WHERE specs->>'ram' = '36GB'
AND price < 40000;
-- JSON 欄位建索引(GIN 索引)
CREATE INDEX idx_products_specs ON products USING GIN (specs);
-- 用 @> 運算子查包含關係(走 GIN 索引)
SELECT * FROM products
WHERE specs @> '{"cpu": "M4 Pro"}';關鍵差異:你可以把「確定的結構」用正常欄位存(有 constraint、有型別檢查),「不確定的部分」用 JSONB 存。兩全其美。
正面對決比較表
| 面向 | MongoDB | PostgreSQL |
|---|---|---|
| ACID 交易 | 4.0+ 支援,效能較差 | 原生支援,成熟穩定 |
| Schema | 動態(自由但危險) | 嚴格 + JSONB 彈性 |
| JOIN | $lookup(有限制) | 完整 JOIN 支援 |
| 查詢語言 | MQL(JSON-based) | SQL(通用標準) |
| 水平擴展 | 內建 Sharding | 需要 Citus 或手動 |
| 垂直效能 | 中 | 高 |
| 全文搜尋 | Atlas Search | tsvector / pg_trgm |
| 地理查詢 | 內建 GeoJSON | PostGIS(業界標準) |
| 彈性結構 | 原生優勢 | JSONB(夠用但稍麻煩) |
| 工具生態 | Compass, Atlas | pgAdmin, DBeaver, 任何 SQL 工具 |
| ORM 支援 | Mongoose | 所有主流 ORM |
| 託管服務 | Atlas | RDS, Cloud SQL, Supabase, Neon |
| 學習資源 | 多 | 極多 |
MongoDB 真正適合的場景
公平地說,有些場景 MongoDB 確實比 PostgreSQL 更合適:
1. 內容管理系統(CMS)
文章、頁面、widget——每種內容的結構都不同,而且經常變動。用 MongoDB 存,改結構不用跑 migration。
// CMS 場景:每篇文章結構可能不同
db.pages.insertOne({
type: "blog",
title: "Hello World",
body: "...",
blocks: [
{ type: "text", content: "..." },
{ type: "image", url: "...", alt: "..." },
{ type: "code", language: "python", content: "..." }
]
});2. IoT 事件日誌
大量寫入、結構不固定、很少需要 JOIN。
3. 快速原型 / MVP
不確定 schema 長什麼樣子,先快速迭代。但請在確定 schema 後遷到 PostgreSQL。
4. 遊戲玩家資料
每個玩家的裝備、成就、進度結構差異大,嵌套很深。
MongoDB 不適合的場景
| 場景 | 為什麼不適合 |
|---|---|
| 電商 / 金融 | 交易一致性是生命線,MongoDB 的 ACID 支援太晚太弱 |
| 複雜關聯 | 使用者 → 訂單 → 商品 → 評論,多層 JOIN 是日常需求 |
| 報表分析 | GROUP BY、Window Functions、CTE——SQL 的天下 |
| 多表聚合查詢 | Aggregation Pipeline 寫起來比 SQL 痛苦十倍 |
| 需要嚴格資料完整性 | 外鍵約束、CHECK 約束、UNIQUE 約束——MongoDB 都沒有 |
Firestore 與 DynamoDB:託管替代方案
如果你考慮 Document DB 但不想管 infra:
| 特性 | Firestore | DynamoDB | MongoDB Atlas |
|---|---|---|---|
| 雲端 | GCP | AWS | 多雲 |
| 定價模型 | 按讀寫次數 | 按讀寫容量 | 按節點規格 |
| 即時同步 | 原生支援 | DynamoDB Streams | Change Streams |
| 離線支援 | 原生 | 無 | 無 |
| 適合 | 行動 App、小型 Web | 大規模、低延遲 | 任何 MongoDB 場景 |
| 學習曲線 | 低 | 中高(分區鍵設計) | 低 |
| 避免 | 複雜查詢(索引限制) | 複雜查詢 | 需要 JOIN 的場景 |
Firestore 適合前端直連(有內建安全規則),做行動 App 或即時協作很方便。 DynamoDB 適合超大規模、低延遲的 key-value 查詢,但分區鍵設計是門學問。
遷移故事:為什麼都是 MongoDB 搬回 PostgreSQL?
這不是偏見,是統計事實。常見的遷移劇本:
- 原型期:用 MongoDB 快速開發,schema 隨便改
- 成長期:開始需要 JOIN、交易、複雜查詢,
$lookup越寫越痛苦 - 成熟期:需要報表、需要分析、需要資料完整性,MongoDB 全部做不好
- 遷移:痛苦地把資料搬回 PostgreSQL,重寫 query layer
反過來的案例(PostgreSQL → MongoDB)極少見,因為:
- PostgreSQL 有 JSONB,彈性需求已被滿足
- SQL 的表達能力遠超 MQL
- PostgreSQL 的生態(備份、監控、工具)更成熟
經典引言:「MongoDB is web scale」已經從讚美變成了 meme。
常見問題
PostgreSQL 的 JSONB 效能好嗎?
比 MongoDB 的原生 document 查詢稍慢(因為 JSONB 是二進位格式但存在 row 裡),但搭配 GIN 索引後差距不大。大部分應用場景感覺不出差異。關鍵是 JSONB 讓你在需要結構化的地方用欄位,需要彈性的地方用 JSON。
MongoDB Atlas 免費方案夠用嗎?
Atlas M0(免費)給 512MB 儲存,做個人專案或原型夠用。但限制很多:沒有備份排程、沒有 performance advisor、共享叢集效能不穩定。真的要上生產,M10 起跳(大約每月 60 美金)。
如果團隊只會 MongoDB 怎麼辦?
學 SQL。認真的。SQL 是資料領域的共通語言,MongoDB 的 MQL 只能在 MongoDB 裡用。花兩週學 SQL 的投資報酬率遠高於繼續在 MQL 的 aggregation pipeline 裡掙扎。
Mongoose 的 schema 不就解決了「schema-less 的問題」嗎?
部分解決。Mongoose 在應用層做 schema 驗證,但它不能防止有人直接用 MongoDB shell 寫入不合格的資料。而且 Mongoose 的 schema 不是資料庫層級的約束,migration 管理也比不上 Prisma / TypeORM 對 PostgreSQL 的支援。
MongoDB 7.0 / 8.0 有改善嗎?
有。MongoDB 持續在補強弱點——queryable encryption、column store index、更好的 aggregation pipeline 效能。但核心設計(document model)的限制還在:沒有外鍵約束、JOIN 效能不佳、schema 驗證是可選的。這些不是版本更新能根本解決的。