Back to blog

Cơ Bản Về Cơ Sở Dữ Liệu Quan Hệ: Database Thực Sự Hoạt Động Như Thế Nào

databasepostgresqlsqlbackend
Cơ Bản Về Cơ Sở Dữ Liệu Quan Hệ: Database Thực Sự Hoạt Động Như Thế Nào

Giới Thiệu

Bạn viết SELECT * FROM users WHERE id = 1 và nhận được kết quả. Nhưng thực sự điều gì xảy ra bên trong database giữa câu query và response? Database tìm một dòng đó giữa hàng triệu dòng bằng cách nào? Tại sao một số query chạy trong vài mili-giây trong khi những query khác mất vài phút?

Hầu hết backend developer tương tác với database thông qua ORM và không bao giờ tìm hiểu sâu hơn. Điều đó ổn — cho đến khi query bị chậm, dữ liệu bị hỏng, hoặc bạn cần thiết kế schema không sụp đổ khi scale.

Hướng dẫn này đưa bạn vào bên trong database engine. Bạn sẽ hiểu mô hình quan hệ, cách query thực sự thực thi, ACID thực sự có nghĩa là gì, và cách đưa ra quyết định đúng đắn về data types, constraints, và database engines.

Bạn Sẽ Học Được Gì

✅ Hiểu mô hình quan hệ — tables, rows, columns, và relationships
✅ Xem điều gì xảy ra bên trong database khi bạn chạy SQL query
✅ Nắm vững primary keys, foreign keys, và constraints
✅ Chọn đúng data types cho các columns
✅ Hiểu thuộc tính ACID với ví dụ thực tế
✅ Học transactions và isolation levels
✅ Biết khi nào dùng PostgreSQL vs MySQL vs SQLite
✅ Thiết kế schema e-commerce đơn giản từ đầu


Mô Hình Quan Hệ (Relational Model)

Năm 1970, Edgar F. Codd công bố bài báo thay đổi ngành khoa học máy tính. Ông đề xuất tổ chức dữ liệu thành quan hệ (tables) với tuples (rows) và attributes (columns). Đây trở thành mô hình quan hệ — nền tảng của gần như mọi database bạn sử dụng ngày nay.

Tables, Rows, và Columns

Một table (hay quan hệ) là tập hợp dữ liệu có cấu trúc về một thực thể cụ thể:

CREATE TABLE users (
    id          SERIAL PRIMARY KEY,
    username    VARCHAR(50) NOT NULL UNIQUE,
    email       VARCHAR(255) NOT NULL UNIQUE,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
Khái niệmTên khácMô tả
TableRelationTập hợp dữ liệu liên quan (vd: users, orders)
RowTuple, RecordMột bản ghi trong table (vd: một user)
ColumnAttribute, FieldThuộc tính của thực thể (vd: username, email)
Schema—Định nghĩa cấu trúc table (tên cột, kiểu dữ liệu, constraints)

Quan Hệ Giữa Các Tables

Từ "quan hệ" trong cơ sở dữ liệu quan hệ đề cập đến cách các tables liên kết với nhau.

One-to-Many (phổ biến nhất):

-- Một user có nhiều orders
CREATE TABLE orders (
    id         SERIAL PRIMARY KEY,
    user_id    INT NOT NULL REFERENCES users(id),
    total      DECIMAL(10, 2) NOT NULL,
    status     VARCHAR(20) NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

One-to-One:

-- Một user có một profile
CREATE TABLE profiles (
    id      SERIAL PRIMARY KEY,
    user_id INT NOT NULL UNIQUE REFERENCES users(id),
    bio     TEXT,
    avatar  VARCHAR(500)
);

Many-to-Many:

-- Products có nhiều tags, tags có nhiều products
CREATE TABLE products (
    id   SERIAL PRIMARY KEY,
    name VARCHAR(200) NOT NULL
);
 
CREATE TABLE tags (
    id   SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);
 
-- Bảng trung gian (junction table)
CREATE TABLE product_tags (
    product_id INT NOT NULL REFERENCES products(id),
    tag_id     INT NOT NULL REFERENCES tags(id),
    PRIMARY KEY (product_id, tag_id)
);

Điều Gì Xảy Ra Khi Bạn Chạy SQL Query

Khi bạn thực thi một query, database không chỉ đơn giản "tìm" dữ liệu. Nó đi qua pipeline nhiều giai đoạn.

Pipeline Thực Thi Query

SQL Query
   │
   ▼
┌──────────┐
│  Parser  │  → Kiểm tra cú pháp, xây dựng parse tree
└──────────┘
   │
   ▼
┌──────────┐
│ Analyzer │  → Xác nhận tên table/column, giải quyết kiểu dữ liệu
└──────────┘
   │
   ▼
┌──────────┐
│ Planner  │  → Tạo các execution plans, chọn plan rẻ nhất
└──────────┘
   │
   ▼
┌──────────┐
│ Executor │  → Chạy plan đã chọn, trả về kết quả
└──────────┘
   │
   ▼
┌──────────┐
│ Storage  │  → Đọc/ghi data pages trên disk
└──────────┘

Parser

Parser kiểm tra SQL có đúng cú pháp không và chuyển thành parse tree:

-- Parse thành công
SELECT username FROM users WHERE id = 1;
 
-- Lỗi tại parser (sai cú pháp)
SELEC username FORM users WHER id = 1;
-- ERROR: syntax error at or near "SELEC"

Analyzer

Analyzer kiểm tra tables và columns có tồn tại không, giải quyết kiểu dữ liệu, và mở rộng wildcards như *.

Planner (Query Optimizer)

Đây là giai đoạn thú vị nhất. Planner tạo nhiều execution plans và chọn plan có chi phí ước tính thấp nhất.

Bạn có thể xem quyết định của planner bằng EXPLAIN:

EXPLAIN SELECT * FROM users WHERE id = 1;
 
-- Output:
-- Index Scan using users_pkey on users  (cost=0.29..8.30 rows=1 width=100)
--   Index Cond: (id = 1)

Executor và Storage Engine

Executor chạy plan đã chọn. Storage engine quản lý cách dữ liệu được lưu trữ vật lý trên disk trong các pages (thường là 8KB). Nó sử dụng buffer cache trong bộ nhớ để tăng tốc truy xuất.


Primary Keys, Foreign Keys, và Constraints

Constraints là các quy tắc được database thực thi để giữ dữ liệu nhất quán.

Primary Key

Mỗi table nên có primary key — cột (hoặc tổ hợp cột) xác định duy nhất mỗi row.

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    username VARCHAR(50) NOT NULL
);

Foreign Key

Foreign key tạo liên kết giữa hai tables và thực thi referential integrity:

CREATE TABLE orders (
    id      SERIAL PRIMARY KEY,
    user_id INT NOT NULL REFERENCES users(id),
    total   DECIMAL(10, 2) NOT NULL
);
 
-- Thành công (user 1 tồn tại)
INSERT INTO orders (user_id, total) VALUES (1, 99.99);
 
-- Thất bại (user 999 không tồn tại)
INSERT INTO orders (user_id, total) VALUES (999, 99.99);
-- ERROR: violates foreign key constraint

CASCADE behavior:

-- ON DELETE CASCADE  → Xóa user sẽ xóa luôn orders
-- ON DELETE RESTRICT → Ngăn xóa user nếu có orders (mặc định)
-- ON DELETE SET NULL → Đặt user_id thành NULL khi user bị xóa

Các Constraints Khác

  • NOT NULL: Ngăn dữ liệu bị thiếu — mặc định nên NOT NULL, chỉ cho phép NULL khi có lý do
  • UNIQUE: Đảm bảo không trùng lặp (vd: email, username)
  • CHECK: Xác thực giá trị (CHECK (price > 0))
  • DEFAULT: Cung cấp giá trị mặc định (DEFAULT NOW())

Chọn Đúng Data Types

Numeric Types

KiểuDung lượngPhạm viUse Case
SMALLINT2 bytes-32,768 đến 32,767Status codes
INTEGER4 bytes-2.1 tỷ đến 2.1 tỷHầu hết IDs
BIGINT8 bytes±9.2 quintillionIDs bảng lớn
DECIMAL(p,s)Thay đổiTùy chỉnhTiền, tính chính xác

Quan trọng: Không bao giờ dùng REAL hoặc DOUBLE PRECISION cho tiền:

-- ❌ SAI: Floating point cho tiền
SELECT 0.1 + 0.2;  -- 0.30000000000000004
 
-- ✅ ĐÚNG: DECIMAL cho tiền
SELECT 0.1::DECIMAL(10,2) + 0.2::DECIMAL(10,2);  -- 0.30

String Types

  • VARCHAR(n): Chuỗi có giới hạn — dùng cho username, email
  • TEXT: Chuỗi không giới hạn — dùng cho blog content, descriptions

Trong PostgreSQL, VARCHAR và TEXT có hiệu suất giống nhau.

Date và Time Types

Luôn dùng TIMESTAMPTZ (timestamp with time zone):

-- ❌ SAI: Không có timezone
created_at TIMESTAMP NOT NULL DEFAULT NOW()
 
-- ✅ ĐÚNG: Có timezone
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()

JSON/JSONB

CREATE TABLE products (
    id       SERIAL PRIMARY KEY,
    name     VARCHAR(200) NOT NULL,
    metadata JSONB DEFAULT '{}'::JSONB
);

Khi nào dùng JSONB: Thuộc tính linh hoạt khác nhau mỗi row, dữ liệu từ API bên ngoài.

Khi nào KHÔNG dùng JSONB: Dữ liệu bạn thường xuyên filter, sort, hoặc join — dùng columns thông thường.


Thuộc Tính ACID

ACID đảm bảo transactions trong database đáng tin cậy.

A — Atomicity (Tính Nguyên Tử)

Transaction là all-or-nothing. Tất cả thành công, hoặc tất cả thất bại.

BEGIN;
UPDATE accounts SET balance = balance - 100 WHERE user_id = 1; -- Alice
UPDATE accounts SET balance = balance + 100 WHERE user_id = 2; -- Bob
COMMIT;
-- Cả hai update thành công, hoặc không cái nào thực hiện

C — Consistency (Tính Nhất Quán)

Database chuyển từ trạng thái hợp lệ này sang trạng thái hợp lệ khác. Constraints không bao giờ bị vi phạm.

I — Isolation (Tính Cô Lập)

Các transactions đồng thời không ảnh hưởng lẫn nhau. Mỗi transaction thấy snapshot nhất quán.

D — Durability (Tính Bền Vững)

Khi transaction đã commit, dữ liệu tồn tại qua mọi sự cố — mất điện, crash, hỏng phần cứng. Database dùng Write-Ahead Log (WAL) để đảm bảo điều này.


Transactions và Isolation Levels

Transaction Là Gì?

Transaction là nhóm operations thực thi như một đơn vị duy nhất:

BEGIN;
INSERT INTO orders (user_id, total) VALUES (1, 150.00);
INSERT INTO order_items (order_id, product_id, quantity) VALUES (1, 42, 2);
UPDATE products SET stock = stock - 2 WHERE id = 42;
COMMIT;

Isolation Levels

LevelDirty ReadsNon-repeatable ReadsPerformance
Read UncommittedCó thểCó thểNhanh nhất
Read CommittedKhôngCó thểTốt (mặc định)
Repeatable ReadKhôngKhôngTrung bình
SerializableKhôngKhôngChậm nhất

Khuyến nghị: Dùng Read Committed (mặc định) cho hầu hết ứng dụng. Dùng Serializable chỉ cho giao dịch tài chính quan trọng.


PostgreSQL vs MySQL vs SQLite

Tính năngPostgreSQLMySQLSQLite
LoạiServer-basedServer-basedEmbedded (file)
Concurrent writesXuất sắcTốtYếu
Advanced SQLĐầy đủHầu hếtCơ bản
JSONBNative, indexableJSON (yếu hơn)JSON functions
Full-text searchBuilt-inBuilt-inExtension
Tốt nhất choProduction chungWeb apps đọc nhiềuDev/test/embedded

PostgreSQL: Lựa chọn mặc định cho mọi ứng dụng production. Nhiều tính năng nâng cao nhất.

MySQL: Rất nhanh cho read queries đơn giản. Phổ biến trong hệ sinh thái PHP/WordPress.

SQLite: Không cần server, chỉ là một file. Hoàn hảo cho development, testing, mobile apps.


Thực Hành: Thiết Kế Schema E-Commerce

Áp dụng mọi thứ đã học bằng cách thiết kế schema cho ứng dụng e-commerce đơn giản.

-- Users
CREATE TABLE users (
    id         SERIAL PRIMARY KEY,
    email      VARCHAR(255) NOT NULL UNIQUE,
    username   VARCHAR(50) NOT NULL UNIQUE,
    password   VARCHAR(255) NOT NULL,
    is_active  BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
 
-- Categories (phân cấp)
CREATE TABLE categories (
    id        SERIAL PRIMARY KEY,
    name      VARCHAR(100) NOT NULL,
    parent_id INT REFERENCES categories(id),
    UNIQUE (name, parent_id)
);
 
-- Products
CREATE TABLE products (
    id          SERIAL PRIMARY KEY,
    name        VARCHAR(200) NOT NULL,
    description TEXT,
    price       DECIMAL(10, 2) NOT NULL CHECK (price > 0),
    stock       INT NOT NULL DEFAULT 0 CHECK (stock >= 0),
    category_id INT REFERENCES categories(id),
    is_active   BOOLEAN NOT NULL DEFAULT TRUE,
    metadata    JSONB DEFAULT '{}'::JSONB,
    created_at  TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
 
-- Tags (many-to-many)
CREATE TABLE tags (
    id   SERIAL PRIMARY KEY,
    name VARCHAR(50) NOT NULL UNIQUE
);
 
CREATE TABLE product_tags (
    product_id INT NOT NULL REFERENCES products(id) ON DELETE CASCADE,
    tag_id     INT NOT NULL REFERENCES tags(id) ON DELETE CASCADE,
    PRIMARY KEY (product_id, tag_id)
);
 
-- Orders
CREATE TABLE orders (
    id         SERIAL PRIMARY KEY,
    user_id    INT NOT NULL REFERENCES users(id),
    status     VARCHAR(20) NOT NULL DEFAULT 'pending'
               CHECK (status IN ('pending', 'confirmed', 'shipped', 'delivered', 'cancelled')),
    total      DECIMAL(10, 2) NOT NULL CHECK (total >= 0),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
 
-- Order Items
CREATE TABLE order_items (
    id         SERIAL PRIMARY KEY,
    order_id   INT NOT NULL REFERENCES orders(id) ON DELETE CASCADE,
    product_id INT NOT NULL REFERENCES products(id),
    quantity   INT NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(10, 2) NOT NULL CHECK (unit_price > 0),
    UNIQUE (order_id, product_id)
);

Quyết Định Thiết Kế Quan Trọng

  1. unit_price trong order_items: Lưu giá tại thời điểm mua, không tham chiếu giá hiện tại
  2. Status dùng CHECK constraint: Database ngăn giá trị không hợp lệ
  3. Categories tự tham chiếu: parent_id cho phân cấp
  4. JSONB cho metadata: Thuộc tính biến đổi mà không cần thêm nhiều nullable columns

Lỗi Thường Gặp Cần Tránh

  1. Dùng sai kiểu ID — Dùng BIGSERIAL cho tables có thể lớn
  2. Thiếu NOT NULL — Mặc định nên NOT NULL, chỉ cho phép NULL khi cần
  3. Lưu tiền dạng float — Luôn dùng DECIMAL cho tiền
  4. Không có foreign keys — Database nên thực thi referential integrity
  5. TIMESTAMP không có timezone — Luôn dùng TIMESTAMPTZ

Tóm Tắt và Điểm Chính

✅ Mô hình quan hệ tổ chức dữ liệu thành tables với rows và columns, liên kết bằng relationships
✅ SQL queries đi qua 5 giai đoạn: parser → analyzer → planner → executor → storage
✅ Query planner là chìa khóa hiệu suất — nó chọn cách truy cập dữ liệu
✅ Constraints bảo vệ tính toàn vẹn dữ liệu ở tầng database
✅ Chọn data types cẩn thận: DECIMAL cho tiền, TIMESTAMPTZ cho ngày giờ
✅ ACID đảm bảo transactions đáng tin cậy
✅ Read Committed là mặc định phù hợp cho hầu hết ứng dụng
✅ PostgreSQL là lựa chọn mặc định tốt nhất cho production
✅ Dùng transactions cho operations nhiều bước phải thành công hoặc thất bại cùng nhau


Tiếp Theo

Bây giờ bạn đã hiểu cách database hoạt động, tiếp tục với các bài tiếp theo trong series:

📚 Database Schema Design Guide — Học normalization, denormalization, và design patterns
📚 SQL Indexing Deep Dive — Làm queries nhanh với indexes đúng
📚 SQL Query Optimization Guide — Viết queries hiệu quả và tránh pitfalls


Kết Luận

Database không phải hộp đen. Hiểu cách chúng hoạt động bên trong — từ query execution pipeline đến ACID — giúp bạn trở thành backend developer tốt hơn. Bạn sẽ viết schema tốt hơn, debug vấn đề hiệu suất nhanh hơn, và đưa ra quyết định đúng đắn.

Mô hình quan hệ đã tồn tại hơn 50 năm vì nó hoạt động. Tables, relationships, constraints, và transactions cung cấp nền tảng đáng tin cậy cho ứng dụng ở mọi quy mô. Nắm vững những kiến thức nền tảng này, và mọi thứ khác — ORMs, migrations, optimization — sẽ dễ hiểu hơn nhiều.

Bắt đầu với PostgreSQL, dùng constraints thoải mái, và luôn bọc operations nhiều bước trong transactions.

📬 Đăng ký nhận bản tin

Nhận những bài viết mới nhất qua email mỗi tuần. Không spam, hủy đăng ký bất cứ lúc nào.

Chúng tôi tôn trọng quyền riêng tư của bạn. Hủy đăng ký bất cứ lúc nào.

💬 Comments

Đăng nhập để bình luận

Chúng tôi sẽ không đăng bất cứ điều gì mà không có sự cho phép của bạn.