⚖️ SQL vs NoSQL
📖 定義
SQL(關聯式資料庫)以表格形式儲存結構化資料,並使用SQL語言操作。NoSQL(非關聯式資料庫)使用彈性schema儲存各種形式的資料,有利於水平擴展。各有優缺點,需根據專案需求選擇。
🎯 透過比喻理解
圖書館 vs 倉庫
SQL (關聯式DB) = 有序圖書館
├─ 所有書籍分類在固定位置
├─ 圖書卡片上記錄精確資訊
├─ 嚴格的借還規則
└─ 容易找到但彈性低
NoSQL (非關聯式DB) = 彈性倉庫
├─ 可存放各種形狀的物品
├─ 快速新增/刪除
├─ 空間擴展容易
└─ 彈性但組織可能困難
學校名冊 vs 社群媒體
SQL = 學校出席簿
學生表:
┌────┬─────────┬─────┬────────┐
│ ID │ 姓名 │年齡 │ 班級 │
├────┼─────────┼─────┼────────┤
│ 1 │ 王小明 │ 15 │ 1班 │
│ 2 │ 李小華 │ 15 │ 2班 │
└────┴─────────┴─────┴────────┘
- 所有學生都有相同的資訊欄位
NoSQL = 社群媒體個人檔案
使用者1: { name: "王小明", age: 15, hobbies: ["閱讀", "遊戲"] }
使用者2: { name: "李小華", city: "台北", job: "學生", pets: 2 }
- 每個使用者可以有不同的資訊
⚙️ 運作原理
1. 資料結構比較
-- SQL: 結構化表格
users 表
┌────┬─────────┬──────────────────┬─────┐
│ id │ name │ email │ age │
├────┼─────────┼──────────────────┼─────┤
│ 1 │ 王小明 │ wang@mail.com │ 25 │
│ 2 │ 李小華 │ lee@mail.com │ 30 │
└────┴─────────┴──────────────────┴─────┘
orders 表
┌────┬─────────┬─────────────┬───────┐
│ id │ user_id │ product │ price │
├────┼─────────┼─────────────┼───────┤
│ 1 │ 1 │ 筆記型電腦 │ 1500 │
│ 2 │ 1 │ 滑鼠 │ 30 │
└────┴─────────┴─────────────┴───────┘
// NoSQL: 彈性文件
// MongoDB 範例
{
_id: ObjectId("507f1f77bcf86cd799439011"),
name: "王小明",
email: "wang@mail.com",
age: 25,
orders: [ // 巢狀文件
{
product: "筆記型電腦",
price: 1500,
date: "2024-01-15"
},
{
product: "滑鼠",
price: 30,
date: "2024-01-20"
}
],
preferences: { // 各種結構
theme: "dark",
notifications: true
}
}
2. NoSQL資料庫類型
1. Document DB (文件型)
└─ MongoDB, CouchDB
└─ 儲存JSON文件
2. Key-Value DB (鍵值型)
└─ Redis, DynamoDB
└─ 快速快取、會話儲存
3. Column-Family DB (欄型)
└─ Cassandra, HBase
└─ 大規模資料分析
4. Graph DB (圖型)
└─ Neo4j, Amazon Neptune
└─ 關係中心資料(社交網路)
💡 實際範例
SQL 範例 (MySQL)
-- 建立表格(嚴格schema)
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(50) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL,
age INT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT,
product VARCHAR(100),
price DECIMAL(10, 2),
FOREIGN KEY (user_id) REFERENCES users(id)
);
-- 插入資料
INSERT INTO users (name, email, age)
VALUES ('王小明', 'wang@mail.com', 25);
-- 用JOIN取得關係
SELECT
users.name,
orders.product,
orders.price
FROM users
INNER JOIN orders ON users.id = orders.user_id
WHERE users.name = '王小明';
-- 交易(ACID保證)
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 1;
UPDATE accounts SET balance = balance + 100 WHERE id = 2;
COMMIT;
NoSQL 範例 (MongoDB)
// 無schema - 自由結構
db.users.insertOne({
name: "王小明",
email: "wang@mail.com",
age: 25,
orders: [
{ product: "筆記型電腦", price: 1500 },
{ product: "滑鼠", price: 30 }
]
});
// 不同結構也可以!
db.users.insertOne({
name: "李小華",
email: "lee@mail.com",
hobbies: ["閱讀", "旅行"], // 不同欄位
address: { // 巢狀物件
city: "台北",
zipcode: "10001"
}
});
// 查詢
db.users.find({ name: "王小明" });
// 在巢狀文件中查詢
db.users.find({
"orders.product": "筆記型電腦"
});
// 更新
db.users.updateOne(
{ name: "王小明" },
{
$set: { age: 26 },
$push: {
orders: { product: "鍵盤", price: 80 }
}
}
);
// 聚合
db.users.aggregate([
{ $unwind: "$orders" },
{ $group: {
_id: "$name",
totalSpent: { $sum: "$orders.price" }
}
}
]);
Node.js中的SQL vs NoSQL
// ============ SQL (MySQL) ============
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'myapp'
});
// 取得使用者與訂單(需要JOIN)
async function getUserWithOrders(userId) {
const [rows] = await pool.execute(`
SELECT
users.name,
users.email,
orders.product,
orders.price
FROM users
LEFT JOIN orders ON users.id = orders.user_id
WHERE users.id = ?
`, [userId]);
return rows;
}
// ============ NoSQL (MongoDB) ============
const { MongoClient } = require('mongodb');
const client = new MongoClient('mongodb://localhost:27017');
const db = client.db('myapp');
const users = db.collection('users');
// 取得使用者與訂單(不需JOIN - 已包含)
async function getUserWithOrders(userId) {
const user = await users.findOne({ _id: userId });
// 訂單資訊已在user.orders中!
return user;
}
實務範例:部落格系統
// ============ SQL方式 ============
// 正規化結構
/*
posts 表:
id, title, content, author_id, created_at
comments 表:
id, post_id, user_id, content, created_at
tags 表:
id, name
post_tags 表:
post_id, tag_id
*/
// 取得文章+留言+標籤 → 需要多個JOIN
SELECT
posts.*,
comments.content as comment,
tags.name as tag
FROM posts
LEFT JOIN comments ON posts.id = comments.post_id
LEFT JOIN post_tags ON posts.id = post_tags.post_id
LEFT JOIN tags ON post_tags.tag_id = tags.id
WHERE posts.id = 1;
// ============ NoSQL方式 ============
// 非正規化結構
{
_id: ObjectId("..."),
title: "MongoDB介紹",
content: "MongoDB是...",
author: {
id: "user123",
name: "王小明"
},
comments: [
{
user: { id: "user456", name: "李小華" },
content: "很棒的文章!",
createdAt: "2024-01-15"
}
],
tags: ["資料庫", "NoSQL"],
createdAt: "2024-01-10"
}
// 一次查詢取得所有資料!
db.posts.findOne({ _id: ObjectId("...") });
🤔 常見問題
Q1. 應該選擇哪個?
A: 根據專案特性選擇:
✅ 選擇SQL的情況:
├─ 資料結構明確且固定
├─ 複雜關係與多個JOIN
├─ ACID交易必須(金融、電商)
├─ 資料完整性重要
└─ 例:銀行系統、會計軟體、ERP
✅ 選擇NoSQL的情況:
├─ 資料結構流動
├─ 快速讀寫重要
├─ 需要水平擴展(scale out)
├─ 大量資料處理
└─ 例:社交媒體、IoT、即時分析、日誌
Q2. 主要差異是什麼?
A:
| 特性 | SQL | NoSQL |
|---|---|---|
| 資料結構 | 表格(列、欄) | 文件、鍵值、圖等 |
| Schema | 固定(嚴格) | 彈性(動態) |
| 擴展 | 垂直擴展(Scale Up) | 水平擴展(Scale Out) |
| 關係 | 用JOIN連接 | 巢狀或參照 |
| 交易 | ACID保證 | 最終一致性(BASE) |
| 查詢 | SQL語言 | 各DB不同 |
| 範例 | MySQL, PostgreSQL | MongoDB, Redis |
// SQL: ACID
Atomicity (原子性): 全有或全無
Consistency (一致性): 不違反規則
Isolation (隔離性): 交易間獨立
Durability (持久性): 永久儲存
// NoSQL: BASE
Basically Available (基本可用)
Soft state (柔性狀態)
Eventually consistent (最終一致性)
Q3. 效能差異?
A: 取決於使用模式:
// SQL優勢:複雜查詢、JOIN
// 從多個表組合資料
SELECT u.name, p.title, c.content
FROM users u
JOIN posts p ON u.id = p.author_id
JOIN comments c ON p.id = c.post_id
WHERE u.age > 20
ORDER BY p.created_at DESC;
// → 強大且精確但可能慢
// NoSQL優勢:快速讀寫
// 所有資訊在一個文件中
db.posts.find({ author_age: { $gt: 20 } })
.sort({ created_at: -1 });
// → 非常快但複雜JOIN困難
// 基準測試範例
SQL: 10,000 讀取/秒 (複雜JOIN)
NoSQL: 100,000 讀取/秒 (簡單查詢)
SQL: 5,000 寫入/秒 (交易保證)
NoSQL: 50,000 寫入/秒 (快速寫入)
Q4. 可以一起使用嗎?
A: 可以!稱為多語言持久化(Polyglot Persistence):
// 例:電商系統
// 1. SQL (MySQL) - 訂單、付款
// → ACID交易必須
{
orders: "MySQL",
payments: "PostgreSQL",
inventory: "MySQL"
}
// 2. NoSQL (MongoDB) - 產品目錄
// → 彈性屬性、快速搜尋
{
products: "MongoDB",
reviews: "MongoDB"
}
// 3. NoSQL (Redis) - 快取、會話
// → 超快速讀寫
{
cache: "Redis",
sessions: "Redis",
realtime: "Redis"
}
// 4. Graph DB (Neo4j) - 推薦系統
// → 關係型查詢
{
recommendations: "Neo4j",
socialGraph: "Neo4j"
}
Q5. 遷移困難嗎?
A: 策略性方法即可:
// SQL → NoSQL 遷移策略
// 1. 混合方法
// - 新功能用NoSQL
// - 既有功能保持SQL
// 2. 漸進式遷移
// 步驟1: 從唯讀資料開始
// 步驟2: 較不重要功能
// 步驟3: 核心功能
// 3. 資料同步
// - 異動資料擷取 (CDC)
// - 事件串流 (Kafka)
// 例:雙重寫入
async function createUser(userData) {
// 寫入SQL
const sqlUser = await mysqlDB.insert(userData);
// 也寫入NoSQL(非同步)
await mongooDB.insertOne({
...userData,
_id: sqlUser.id
}).catch(err => {
// 失敗時SQL仍保留
console.error('MongoDB同步失敗:', err);
});
return sqlUser;
}
🎓 下一步
了解SQL與NoSQL後,繼續學習:
- 什麼是資料庫? - 基本概念
- 什麼是Node.js? - 後端開發
- 什麼是Docker?(文件待編寫) - 資料庫容器化
實作練習
# ============ MySQL 實作 ============
# 安裝
brew install mysql # macOS
# 執行
mysql -u root -p
# 建立表格與查詢
CREATE DATABASE testdb;
USE testdb;
CREATE TABLE users (id INT, name VARCHAR(50));
INSERT INTO users VALUES (1, '王小明');
SELECT * FROM users;
# ============ MongoDB 實作 ============
# 安裝
brew tap mongodb/brew
brew install mongodb-community
# 執行
mongod --config /usr/local/etc/mongod.conf
# MongoDB Shell
mongosh
# 插入與查詢資料
use testdb
db.users.insertOne({ name: "王小明", age: 25 })
db.users.find()
🎬 總結
SQL與NoSQL各有優缺點:
- SQL:結構化資料、ACID交易、複雜關係
- NoSQL:彈性schema、快速效能、水平擴展
- 選擇標準:資料結構、擴展性、一致性需求
- 多語言:依需求組合使用多個DB
「沒有銀彈」- 為您的專案選擇適合的資料庫! ⚖️✨