Bắt đầu từ một sai lầm tốn 3 ngày
Cách đây 2 năm, mình từng migrate một database 100GB từ MySQL sang PostgreSQL — mất 3 ngày chỉ để planning và 1 ngày thực thi. Không phải vì hệ thống phức tạp, mà vì ban đầu team chọn sai công cụ. MySQL được dùng cho dự án có yêu cầu JOIN phức tạp và transaction nghiêm ngặt, nhưng schema lại thiết kế theo kiểu NoSQL — linh hoạt, không ràng buộc. Kết quả: query chậm, performance tệ, và tốn hàng tuần refactor.
Nghe như lỗi sơ đẳng. Nhưng đây chính xác là quyết định kiến trúc mà nhiều team đưa ra trong vài phút đầu dự án — rồi trả giá trong vài tháng sau.
Vấn đề thực tế: Tại sao chọn sai database lại đau?
Thử hình dung tình huống này: bạn build e-commerce, chọn MongoDB vì “NoSQL nhanh và linh hoạt”. Đến khi cần báo cáo doanh thu — JOIN orders với users, tính tổng theo category, filter theo date range — phải viết aggregation pipeline dài cả trang. Debug mất nửa ngày chỉ để ra con số mà SQL trả về trong 3 dòng.
Chiều ngược lại cũng không khá hơn. Dùng MySQL cho ứng dụng IoT cần ghi 50.000 event/giây từ hàng nghìn thiết bị: schema cứng nhắc, write throughput thấp, chạy được vài tuần là hệ thống bắt đầu ngộp.
Root cause của cả hai: chọn database theo “xu hướng” hoặc “quen tay”, không theo đặc điểm dữ liệu và access pattern thực tế.
Phân tích nguyên nhân: SQL và NoSQL được thiết kế để làm gì?
SQL — Sinh ra để đảm bảo tính toàn vẹn
Relational database — PostgreSQL, MySQL, SQLite, SQL Server — được xây dựng quanh một nguyên tắc: dữ liệu không được phép ở trạng thái mâu thuẫn. Để đảm bảo điều đó, chúng có bốn đặc tính cốt lõi:
- Schema cố định: Mọi row trong table đều có cùng cột, đúng kiểu dữ liệu — không được sai
- ACID transactions: Atomicity, Consistency, Isolation, Durability — dữ liệu không bao giờ ở trạng thái “nửa vời”
- Quan hệ (Relations): Foreign key, JOIN — liên kết dữ liệu chặt chẽ, nhất quán
- Vertical scaling: Mở rộng bằng cách nâng cấp server (RAM, CPU, SSD)
-- Query SQL điển hình: lấy đơn hàng kèm thông tin khách hàng
SELECT
o.id AS order_id,
u.name AS customer_name,
o.total_amount,
o.created_at
FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'completed'
AND o.created_at >= '2025-01-01'
ORDER BY o.created_at DESC;
NoSQL — Sinh ra để mở rộng linh hoạt
NoSQL không phải một công nghệ — đó là nhóm database có chung đặc điểm: không dùng mô hình quan hệ, ưu tiên scale-out thay vì schema cứng. Có 4 nhánh chính, mỗi nhánh giải quyết bài toán hoàn toàn khác nhau:
- Document stores (MongoDB, CouchDB): Lưu document JSON/BSON — phù hợp dữ liệu bán cấu trúc
- Key-Value stores (Redis, DynamoDB): Truy cập cực nhanh qua key — phù hợp cache, session
- Column-family (Cassandra, HBase): Write throughput cực cao — phù hợp time-series, IoT
- Graph databases (Neo4j): Quan hệ phức tạp giữa entities — phù hợp social network, recommendation engine
// MongoDB document — user profile linh hoạt
{
"_id": "usr_001",
"name": "Nguyễn Văn A",
"email": "[email protected]",
"preferences": {
"theme": "dark",
"language": "vi",
"notifications": ["email", "push"]
},
"social_accounts": [
{ "platform": "github", "username": "vana_dev" },
{ "platform": "linkedin", "url": "linkedin.com/in/vana" }
]
}
Schema linh hoạt — không cần ALTER TABLE khi thêm field mới. User A có social_accounts, user B không có — MongoDB không quan tâm. Với PostgreSQL, bạn sẽ phải chạy migration và xử lý giá trị NULL cho toàn bộ row cũ.
Các cách giải quyết: Chọn đúng theo từng bài toán
Khi nào dùng SQL
Mấy bài toán này mà chọn NoSQL thì sẽ rất đau:
- Tài chính, kế toán: Mọi transaction phải đảm bảo ACID — không được phép mất tiền “giữa chừng”
- E-commerce: Orders, inventory, payments — quan hệ phức tạp, cần JOIN thường xuyên
- ERP/CRM: Dữ liệu doanh nghiệp có schema ổn định, ít thay đổi theo thời gian
- Báo cáo phức tạp:
GROUP BY,HAVING, subquery, window functions
-- Window function: tính rank doanh thu theo tháng
SELECT
product_name,
month,
revenue,
RANK() OVER (
PARTITION BY month
ORDER BY revenue DESC
) AS revenue_rank
FROM monthly_sales
WHERE year = 2025;
Khi nào dùng NoSQL
Ngược lại, một số bài toán mà ép vào SQL sẽ rất gượng gạo:
- Real-time applications: Chat, gaming — Redis pub/sub hoặc MongoDB với change streams
- Content management: Blog, product catalog — schema thay đổi thường xuyên theo yêu cầu business
- IoT / Time-series data: Hàng triệu event mỗi ngày — Cassandra hoặc InfluxDB xử lý tốt hơn nhiều
- Session, cache: Redis với TTL tự động xóa session hết hạn, không cần cleanup job
# Redis: lưu session với TTL 30 phút
redis-cli SET "session:usr_001" '{"user_id":1,"role":"admin"}' EX 1800
# Đọc session
redis-cli GET "session:usr_001"
# Kiểm tra thời gian còn lại (giây)
redis-cli TTL "session:usr_001"
# Python: ghi IoT data vào Cassandra — write throughput cực cao
from cassandra.cluster import Cluster
from datetime import datetime
cluster = Cluster(['cassandra-node1', 'cassandra-node2'])
session = cluster.connect('iot_keyspace')
insert_query = """
INSERT INTO sensor_readings (device_id, timestamp, temperature, humidity)
VALUES (%s, %s, %s, %s)
"""
session.execute(insert_query, ('sensor_001', datetime.now(), 28.5, 65.2))
print("Ghi thành công")
Cách tốt nhất: Dùng cả hai theo đúng vai trò (Polyglot Persistence)
Không có ứng dụng production nào đủ phức tạp mà chỉ cần một loại database. Netflix dùng Cassandra cho viewing history, MySQL cho billing, Redis cho session. Uber dùng PostgreSQL cho trip data, Redis cho real-time matching. Đó là Polyglot Persistence — mỗi loại database làm đúng việc của nó, không phải một cái làm tất cả.
Ví dụ kiến trúc cho một ứng dụng e-commerce đầy đủ:
- PostgreSQL: Orders, users, inventory — cần ACID và JOIN chặt chẽ
- Redis: Session, giỏ hàng tạm, rate limiting, cache product detail
- MongoDB: Product catalog — mỗi category có attributes khác nhau, schema linh hoạt
- Elasticsearch: Full-text search sản phẩm, filter đa chiều
Để ra quyết định nhanh, mình thường tự hỏi 3 câu:
- Dữ liệu có cấu trúc cố định không? — Nếu có → SQL
- Cần JOIN hoặc transaction multi-table không? — Nếu có → SQL
- Cần write throughput cao hoặc horizontal scale không? — Nếu có → NoSQL
Chưa chắc cả 3? Bắt đầu với PostgreSQL. Nó hỗ trợ JSONB cho phần linh hoạt, full-text search tích hợp sẵn, và xử lý được hầu hết workload ở quy mô vừa. Sau này nếu cần scale thêm, migrate từng phần sang NoSQL dễ hơn rất nhiều so với refactor toàn bộ schema.
Đó cũng là bài học từ lần migrate 100GB kia: nếu ngay từ đầu dùng PostgreSQL với JSONB cho phần linh hoạt và schema chuẩn cho phần cần ràng buộc, mình không cần migrate gì cả — và tiết kiệm được ít nhất 4 ngày công.

