Database Design & Management: Nền tảng của Mọi Ứng dụng

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.

Lý thuyết Cơ sở Dữ liệu Cốt lõi

ACID Properties - Nền tảng của Relational Databases

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:

  • Read Uncommitted: Đọc được dirty data (chưa commit) → rủi ro cao
  • Read Committed: Chỉ đọc committed data → phổ biến nhất
  • Repeatable Read: Đảm bảo đọc lại cùng data trong 1 transaction
  • Serializable: Strictest - như chạy tuần tự → chậm nhất

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 Model - Fундаментum của NoSQL

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

  • Bạn like một post → update ngay lập tức trên cache
  • Số likes có thể khác nhau nếu xem từ regions khác nhau
  • Sau vài giây/phút, mọi nơi đều đồng bộ

Khi nào dùng BASE: Social media, analytics, caching, systems cần high availability.

Định lý CAP - Lựa chọn Không thể Hoàn hảo

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ại sao không thể có cả 3?

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)

  • Impossible trong thực tế! Network partition là điều không tránh khỏi trong distributed systems.

Scenario 2: Chọn C + P (bỏ A) - CP Systems

  • Khi network partition xảy ra, hệ thống từ chối requests để đảm bảo consistency
  • Ví dụ: MongoDB (trong mode majority), HBase, Redis Cluster
  • Trade-off: Downtime trong partition

Scenario 3: Chọn A + P (bỏ C) - AP Systems

  • Khi partition, cả DC1 và DC2 vẫn serve requests, nhưng data có thể inconsistent
  • Ví dụ: Cassandra, DynamoDB, Couchbase
  • Trade-off: Eventual consistency

Lựa chọn CAP trong thực tế

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.

SQL vs NoSQL - Cuộc chiến Không hồi kết

Relational Databases (SQL)

Đặc điểm:

  • Schema cố định, tables với rows/columns
  • Relationships qua foreign keys
  • ACID guarantees
  • SQL query language

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 Databases

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:

  • Flexible schema (dễ evolve)
  • Natural fit cho object-oriented programming
  • Embedded documents tránh JOINs

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:

  • Cực kỳ nhanh (O(1) lookups)
  • Simple to scale horizontally

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:

  • Massive scalability (hàng petabytes)
  • High write throughput
  • Distributed by design

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

So sánh SQL vs NoSQL

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:

  • PostgreSQL hỗ trợ JSONB (document-like)
  • MongoDB có multi-document ACID transactions
  • NewSQL (CockroachDB, Google Spanner) kết hợp ưu điểm cả hai

Kỹ thuật Chuẩn hóa (Normalization)

Normalization là quá trình tổ chức data để giảm redundancy và improve integrity.

Normal Forms - Các cấp độ chuẩn hóa

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)
);

Khi nào Denormalize?

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).

Chiến lược Indexing - Tăng tốc Queries

Index giống như mục lục của sách - giúp database tìm data nhanh hơn.

Cách Index hoạt động

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:

  • Self-balancing
  • Logarithmic search time
  • Efficient for range queries (WHERE age BETWEEN 20 AND 30)

Các loại Index

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 Best Practices

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

  • Mỗi index tốn space
  • Slow down INSERT/UPDATE/DELETE (phải update indexes)
  • Rule: Index những columns query thường xuyên

Đừ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;

Key Takeaways

  • ACID (SQL) cho consistency, BASE (NoSQL) cho availability
  • CAP theorem: Chỉ chọn được 2/3 (Consistency, Availability, Partition Tolerance)
  • SQL tốt cho structured data và complex queries; NoSQL tốt cho flexibility và scalability
  • NoSQL có 4 loại: Document, Key-Value, Column-Family, Graph
  • Normalization giảm redundancy (1NF → 2NF → 3NF), nhưng đôi khi cần denormalize cho performance
  • Indexing dramatically tăng query speed, nhưng có trade-offs (space, write performance)
  • Index design quan trọng: composite indexes, leftmost prefix, covering indexes

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