Nếu application là một tòa nhà, thì database chính là nền móng. Một database thiết kế tốt giúp ứng dụng nhanh, đáng tin cậy và dễ mở rộng. Ngược lại, database thiết kế kém có thể biến mọi tính năng mới thành cơn ác mộng.
Trong bài này, chúng ta sẽ khám phá các lý thuyết cốt lõi về database, từ ACID đến CAP theorem, và học cách chọn database phù hợp cho từng use case.
ACID là 4 tính chất đảm bảo transactions được xử lý đáng tin cậy trong relational databases.
A - Atomicity (Tính nguyên tử)
Transaction là "all or nothing" - hoặc tất cả operations thành công, hoặc tất cả fail.
Ví dụ thực tế: Chuyển tiền từ tài khoản A sang B
BEGIN TRANSACTION;
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
UPDATE accounts SET balance = balance + 100 WHERE id = 'B';
COMMIT;
Nếu bước 2 fail (ví dụ network error), bước 1 sẽ được rollback tự động. Tiền không bao giờ "biến mất" giữa hai tài khoản.
C - Consistency (Tính nhất quán)
Transaction chỉ chuyển database từ valid state này sang valid state khác. Mọi constraints, triggers, cascades đều được enforce.
Ví dụ: Nếu bạn có constraint balance >= 0, database sẽ reject transaction làm balance âm:
-- Sẽ fail nếu balance < 100
UPDATE accounts SET balance = balance - 100 WHERE id = 'A';
I - Isolation (Tính cô lập)
Concurrent transactions không ảnh hưởng lẫn nhau. Mỗi transaction cảm thấy như đang chạy một mình.
Isolation levels:
Ví dụ vấn đề Isolation:
Time | Transaction A | Transaction B
-----|------------------------|------------------
T1 | READ balance (100) |
T2 | | READ balance (100)
T3 | UPDATE balance = 50 |
T4 | | UPDATE balance = 80
T5 | COMMIT |
T6 | | COMMIT
Kết quả cuối cùng: balance = 80 (mất update của A!) → Cần isolation level cao hơn.
D - Durability (Tính bền vững)
Sau khi transaction commit, dữ liệu được lưu vĩnh viễn, kể cả khi system crash.
Cơ chế: Databases dùng Write-Ahead Logging (WAL) - ghi log trước khi modify data. Nếu crash, recover từ log.
BASE là alternative của ACID, ưu tiên availability và performance hơn consistency.
BA - Basically Available
Hệ thống luôn available, kể cả khi một số nodes fail. Trade-off: có thể trả về stale data.
S - Soft state
State có thể thay đổi theo thời gian ngay cả khi không có input (do eventual consistency).
E - Eventual consistency
Hệ thống sẽ consistent "sau một khoảng thời gian", không phải ngay lập tức.
Ví dụ thực tế: Facebook Likes
Khi nào dùng BASE: Social media, analytics, caching, systems cần high availability.
CAP theorem (Eric Brewer, 2000) nói rằng trong distributed database, bạn chỉ có thể đạt được 2 trong 3 tính chất:
C - Consistency: Mọi read nhận được latest write
A - Availability: Mọi request đều được phản hồi (kể cả khi một số nodes down)
P - Partition Tolerance: Hệ thống hoạt động kể cả khi có network partition
Tưởng tượng bạn có database replicated ở 2 data centers (DC1 và DC2). Network giữa chúng bị đứt (partition).
Scenario 1: Chọn C + A (bỏ P)
Scenario 2: Chọn C + P (bỏ A) - CP Systems
Scenario 3: Chọn A + P (bỏ C) - AP Systems
| Use Case | Chọn | Database | Lý do |
|---|---|---|---|
| Banking, Finance | CP | PostgreSQL, MySQL | Consistency tối quan trọng |
| Social Media | AP | Cassandra, DynamoDB | Availability quan trọng hơn |
| E-commerce | CA → CP | MySQL + caching | Inventory cần consistency |
| Analytics | AP | Cassandra, BigQuery | Stale data chấp nhận được |
Thực tế: Nhiều systems kết hợp - ví dụ Uber dùng MySQL cho critical data (bookings) và Cassandra cho analytics.
Đặc điểm:
Khi nào dùng:
✅ Data có structure rõ ràng và ít thay đổi
✅ Cần complex queries với JOINs
✅ Transactions quan trọng (banking, e-commerce)
✅ Data integrity là priority
Ví dụ: PostgreSQL, MySQL, SQL Server, Oracle
Sample use case: E-commerce
-- Complex query với JOINs
SELECT o.id, u.name, SUM(oi.price * oi.quantity) as total
FROM orders o
JOIN users u ON o.user_id = u.id
JOIN order_items oi ON oi.order_id = o.id
WHERE o.created_at > '2024-01-01'
GROUP BY o.id, u.name
HAVING total > 1000;
NoSQL không phải "No SQL" mà là "Not Only SQL". Có 4 loại chính:
1. Document Databases (MongoDB, Couchbase)
Store data dưới dạng documents (thường là JSON).
{
"_id": "user123",
"name": "John Doe",
"email": "john@example.com",
"addresses": [
{"type": "home", "street": "123 Main St"},
{"type": "work", "street": "456 Office Blvd"}
],
"orders": [
{"id": "order1", "total": 100, "items": [...]}
]
}
Ưu điểm:
Khi nào dùng: Content management, user profiles, catalogs
2. Key-Value Stores (Redis, DynamoDB)
Đơn giản nhất - chỉ là giant hash table.
# Redis example
SET user:123:name "John Doe"
GET user:123:name # Returns "John Doe"
# Session storage
SET session:abc123 '{"user_id": 123, "cart": [...]}'
EXPIRE session:abc123 3600 # Auto-delete sau 1 giờ
Ưu điểm:
Khi nào dùng: Caching, session storage, real-time analytics, leaderboards
3. Column-Family Stores (Cassandra, HBase)
Store data theo columns thay vì rows, optimize cho write-heavy workloads.
Ưu điểm:
Khi nào dùng: Time-series data, IoT, logging, analytics
4. Graph Databases (Neo4j, Amazon Neptune)
Optimize cho relationships - nodes và edges.
// Neo4j query: Tìm bạn bè của bạn bè
MATCH (me:Person {name: "John"})-[:FRIEND]->(friend)-[:FRIEND]->(fof)
WHERE NOT (me)-[:FRIEND]->(fof) AND me <> fof
RETURN fof.name
Khi nào dùng: Social networks, recommendation engines, fraud detection, knowledge graphs
| Tiêu chí | SQL | NoSQL |
|---|---|---|
| Schema | Fixed, predefined | Flexible, dynamic |
| Scaling | Vertical (bigger machine) | Horizontal (more machines) |
| Transactions | Strong ACID | Eventual consistency (thường) |
| Query | SQL - powerful, complex | Varies by type |
| Use case | Structured data, complex queries | Unstructured, high scalability |
| Learning curve | Steeper (SQL syntax) | Easier (often simple APIs) |
Lưu ý: Ranh giới đang mờ dần:
Normalization là quá trình tổ chức data để giảm redundancy và improve integrity.
1NF (First Normal Form)
Mỗi cell chỉ chứa atomic value (không có lists hoặc sets).
-- ❌ Vi phạm 1NF
CREATE TABLE students (
id INT,
name VARCHAR(100),
courses VARCHAR(500) -- "Math, Physics, Chemistry" - NOT ATOMIC!
);
-- ✅ Đạt 1NF
CREATE TABLE students (
id INT,
name VARCHAR(100)
);
CREATE TABLE student_courses (
student_id INT,
course VARCHAR(100)
);
2NF (Second Normal Form)
Đạt 1NF + mọi non-key attribute phụ thuộc hoàn toàn vào primary key.
-- ❌ Vi phạm 2NF
CREATE TABLE order_items (
order_id INT,
product_id INT,
product_name VARCHAR(100), -- Phụ thuộc vào product_id, không phải (order_id, product_id)
quantity INT,
PRIMARY KEY (order_id, product_id)
);
-- ✅ Đạt 2NF
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);
CREATE TABLE products (
product_id INT PRIMARY KEY,
product_name VARCHAR(100)
);
3NF (Third Normal Form)
Đạt 2NF + không có transitive dependencies (non-key attribute không phụ thuộc vào non-key attribute khác).
-- ❌ Vi phạm 3NF
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100),
dept_id INT,
dept_name VARCHAR(100) -- Phụ thuộc vào dept_id (non-key) → transitive!
);
-- ✅ Đạt 3NF
CREATE TABLE employees (
emp_id INT PRIMARY KEY,
emp_name VARCHAR(100),
dept_id INT
);
CREATE TABLE departments (
dept_id INT PRIMARY KEY,
dept_name VARCHAR(100)
);
Normalization tốt cho data integrity, nhưng có thể làm chậm queries (nhiều JOINs). Đôi khi cần denormalize cho performance.
Ví dụ: E-commerce product catalog
-- Normalized (nhiều JOINs)
SELECT p.name, c.name as category, b.name as brand
FROM products p
JOIN categories c ON p.category_id = c.id
JOIN brands b ON p.brand_id = b.id;
-- Denormalized (fast, nhưng redundant)
CREATE TABLE products (
id INT PRIMARY KEY,
name VARCHAR(100),
category_name VARCHAR(100), -- Duplicate data
brand_name VARCHAR(100) -- Duplicate data
);
Trade-off: Fast reads vs potential inconsistency khi update.
Rule of thumb: Normalize during design, denormalize selectively for performance (sau khi có data từ monitoring).
Index giống như mục lục của sách - giúp database tìm data nhanh hơn.
Hầu hết databases dùng B-Tree hoặc B+ Tree cho indexes.
Without index:
SELECT * FROM users WHERE email = 'john@example.com';
→ Full table scan: O(n) - quét toàn bộ 1 triệu rows
With index on email:
→ Index lookup: O(log n) - chỉ vài lookups
B-Tree properties:
WHERE age BETWEEN 20 AND 30)1. Primary Index
Tự động tạo trên PRIMARY KEY. Data physically sorted theo key này.
2. Secondary Index
Index trên non-primary columns.
CREATE INDEX idx_email ON users(email);
CREATE INDEX idx_created ON orders(created_at);
3. Composite Index
Index trên nhiều columns.
CREATE INDEX idx_name_age ON users(last_name, first_name, age);
-- Sử dụng hiệu quả:
SELECT * FROM users WHERE last_name = 'Smith' AND first_name = 'John';
-- KHÔNG sử dụng index (do leftmost prefix rule):
SELECT * FROM users WHERE first_name = 'John'; -- Missing last_name!
Leftmost Prefix Rule: Composite index (A, B, C) có thể dùng cho queries với (A), (A,B), (A,B,C), nhưng KHÔNG dùng được cho (B), (C), (B,C).
4. Unique Index
Đảm bảo uniqueness.
CREATE UNIQUE INDEX idx_email ON users(email);
-- Prevents duplicate emails
5. Full-Text Index
Cho text search.
-- PostgreSQL
CREATE INDEX idx_content_search ON articles
USING GIN (to_tsvector('english', content));
SELECT * FROM articles
WHERE to_tsvector('english', content) @@ to_tsquery('database & design');
✅ Index columns trong WHERE, JOIN, ORDER BY
-- Query này cần indexes ở đâu?
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id
WHERE o.status = 'pending'
ORDER BY o.created_at DESC;
-- Cần indexes:
-- orders(status), orders(created_at), orders(user_id)
-- users(id) - đã có (primary key)
✅ Avoid over-indexing
❌ Đừng index columns với low cardinality
-- BAD: boolean column chỉ có 2 values
CREATE INDEX idx_active ON users(is_active); -- Không hiệu quả!
-- GOOD: High cardinality column
CREATE INDEX idx_email ON users(email); -- Mỗi email unique
✅ Monitor index usage
-- PostgreSQL: Xem indexes không dùng
SELECT schemaname, tablename, indexname
FROM pg_stat_user_indexes
WHERE idx_scan = 0;
✅ Consider covering indexes
-- Covering index: include tất cả columns cần trong SELECT
CREATE INDEX idx_user_orders ON orders(user_id) INCLUDE (status, total, created_at);
-- Query này chỉ cần index, không cần access table
SELECT status, total, created_at
FROM orders
WHERE user_id = 123;
Trong bài tiếp theo, chúng ta sẽ tìm hiểu API Design & Integration - cách thiết kế APIs RESTful, GraphQL, và các best practices cho authentication/authorization.
Bài viết thuộc series "From Zero to AI Engineer" - Module 2: Design & Architecture