跳至正文

⚖️ 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.com25
2 │ 李小華 │ lee@mail.com30
└────┴─────────┴──────────────────┴─────┘

orders 表
┌────┬─────────┬─────────────┬───────┐
│ id │ user_id │ product │ price │
├────┼─────────┼─────────────┼───────┤
11 │ 筆記型電腦 │ 1500
21 │ 滑鼠 │ 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:

特性SQLNoSQL
資料結構表格(列、欄)文件、鍵值、圖等
Schema固定(嚴格)彈性(動態)
擴展垂直擴展(Scale Up)水平擴展(Scale Out)
關係用JOIN連接巢狀或參照
交易ACID保證最終一致性(BASE)
查詢SQL語言各DB不同
範例MySQL, PostgreSQLMongoDB, 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後,繼續學習:

  1. 什麼是資料庫? - 基本概念
  2. 什麼是Node.js? - 後端開發
  3. 什麼是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

「沒有銀彈」- 為您的專案選擇適合的資料庫! ⚖️✨