1. Database Design là gì?

Database Design (thiết kế cơ sở dữ liệu) là quá trình xác định cách tổ chức và liên kết dữ liệu để đáp ứng yêu cầu nghiệp vụ của một ứng dụng.

Khi xây dựng Backend, chúng ta không chỉ cần viết API mà còn phải xác định dữ liệu nào cần lưu trữ, lưu ở đâu, có cấu trúc như thế nào và liên kết với nhau ra sao.

Ví dụ, khi xây dựng một hệ thống bán hàng, chúng ta cần quản lý:

  • Người dùng (Users).
  • Sản phẩm (Products).
  • Đơn hàng (Orders).
  • Các sản phẩm thuộc từng đơn hàng (Order Items).

Một thiết kế database phù hợp giúp dữ liệu được tổ chức rõ ràng, các mối quan hệ được xác định chính xác và Backend có thể truy vấn dữ liệu theo yêu cầu nghiệp vụ.

Database Design thường bao gồm việc xác định các bảng, cột, kiểu dữ liệu, khóa chính, khóa ngoại, ràng buộc và mối quan hệ giữa các bảng.

2. Các thành phần cơ bản trong Database Design

2.1. Table, Row và Column

Trong cơ sở dữ liệu quan hệ như MySQL và PostgreSQL, dữ liệu thường được tổ chức thành các bảng (Table).

Mỗi bảng gồm các thành phần:

  • Table (bảng): tổ chức dữ liệu về một thực thể hoặc một nhóm thông tin có liên quan.
  • Column (cột): mô tả thuộc tính của dữ liệu.
  • Row/Record (bản ghi): chứa giá trị của một đối tượng cụ thể.

Ví dụ, bảng products có thể được thiết kế như sau:

id name price
101 Keyboard 500000
102 Mouse 200000
103 Monitor 3000000

Trong ví dụ này:

  • products là tên bảng.
  • id, name, price là các cột.
  • Mỗi dòng đại diện cho một sản phẩm.

2.2. Data Types — Kiểu dữ liệu

Mỗi cột cần có kiểu dữ liệu phù hợp với giá trị mà nó lưu trữ.

Một số kiểu dữ liệu phổ biến:

Kiểu dữ liệu Mục đích Ví dụ
INT Số nguyên Số lượng
BIGINT Số nguyên có phạm vi lớn hơn ID
VARCHAR(n) Chuỗi có giới hạn độ dài Tên sản phẩm
TEXT Văn bản dài Mô tả
DECIMAL(p,s) Số thập phân có độ chính xác xác định Giá tiền
DATE Ngày Ngày sinh
TIMESTAMP Ngày và giờ Thời điểm tạo đơn
BOOLEAN Giá trị đúng/sai Trạng thái

Việc lựa chọn kiểu dữ liệu phụ thuộc vào yêu cầu thực tế và khả năng hỗ trợ của hệ quản trị cơ sở dữ liệu.

Chẳng hạn, giá tiền thường phù hợp với DECIMAL hơn kiểu số thực dấu phẩy động, vì cần kiểm soát độ chính xác của giá trị.

2.3. Primary Key — Khóa chính

Primary Key (PK) là cột hoặc tập hợp các cột dùng để xác định duy nhất một bản ghi trong bảng.

Ví dụ:

CREATE TABLE products (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(12, 2) NOT NULL
);

Trong bảng products, id là khóa chính. Mỗi sản phẩm phải có một giá trị id duy nhất và khóa chính không được nhận giá trị NULL.

Khóa chính giúp chúng ta xác định chính xác bản ghi cần truy vấn, cập nhật hoặc xóa.

2.4. Foreign Key — Khóa ngoại

Foreign Key (FK) là cột hoặc tập hợp các cột dùng để tham chiếu đến khóa chính hoặc một khóa có ràng buộc duy nhất ở bảng khác.

Ví dụ, một đơn hàng thuộc về một người dùng:

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Trong ví dụ này, orders.user_id tham chiếu đến users.id.

Nhờ khóa ngoại, database có thể kiểm soát tính hợp lệ của mối quan hệ, chẳng hạn ngăn việc tạo đơn hàng tham chiếu đến một người dùng không tồn tại.

Khác với khóa chính, một bảng có thể có nhiều khóa ngoại để liên kết với nhiều bảng khác nhau.

2.5. Constraints — Ràng buộc dữ liệu

Constraints là những quy tắc được đặt lên bảng hoặc cột để giới hạn các giá trị được phép lưu trữ.

Một số ràng buộc thường gặp:

  • NOT NULL: cột không được chứa giá trị NULL.
  • UNIQUE: các giá trị thuộc phạm vi ràng buộc phải duy nhất.
  • CHECK: yêu cầu dữ liệu thỏa mãn một điều kiện.
  • DEFAULT: cung cấp giá trị mặc định khi không chỉ định giá trị cho cột trong trường hợp phù hợp.
  • PRIMARY KEY: định danh duy nhất một bản ghi.
  • FOREIGN KEY: kiểm soát quan hệ tham chiếu giữa các bảng.

Ví dụ:

CREATE TABLE products (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(12, 2) NOT NULL CHECK (price >= 0)
);

Ràng buộc CHECK thể hiện rằng giá sản phẩm phải lớn hơn hoặc bằng 0. Đây là một quy tắc dữ liệu được thực thi ở database, dù Service của Backend vẫn nên kiểm tra quy tắc nghiệp vụ phù hợp trước khi lưu dữ liệu.

3. Database Relationships là gì?

Database Relationships (mối quan hệ giữa các bảng) mô tả cách các bản ghi trong một bảng liên kết với các bản ghi ở bảng khác.

Trong cơ sở dữ liệu quan hệ, ba loại quan hệ cơ bản thường gặp là One-to-One, One-to-Many và Many-to-Many.

3.1. One-to-One (1:1) — Một-một

Quan hệ One-to-One xảy ra khi một bản ghi ở bảng A liên kết với tối đa một bản ghi ở bảng B, và ngược lại.

Ví dụ: một người dùng có một hồ sơ cá nhân riêng.

Ta có hai bảng:

  • users: lưu thông tin tài khoản.
  • user_profiles: lưu thông tin hồ sơ bổ sung.
CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE user_profiles (
    user_id BIGINT PRIMARY KEY,
    bio TEXT,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Ở đây, user_profiles.user_id vừa là khóa chính của bảng hồ sơ, vừa là khóa ngoại tham chiếu đến users.id.

Vì user_id là khóa chính nên mỗi người dùng chỉ có tối đa một bản ghi hồ sơ, và mỗi hồ sơ phải gắn với một người dùng tồn tại.

Quan hệ này phù hợp khi muốn tách thông tin thành các nhóm riêng, chẳng hạn thông tin tài khoản và thông tin hồ sơ chi tiết.

3.2. One-to-Many (1) — Một-nhiều

Quan hệ One-to-Many xảy ra khi một bản ghi ở bảng A có thể liên kết với nhiều bản ghi ở bảng B, nhưng mỗi bản ghi ở B thuộc về tối đa một bản ghi ở A thông qua quan hệ đó.

Ví dụ: một người dùng có thể tạo nhiều đơn hàng, nhưng mỗi đơn hàng chỉ thuộc về một người dùng.

CREATE TABLE users (
    id BIGINT PRIMARY KEY,
    name VARCHAR(100) NOT NULL
);

CREATE TABLE orders (
    id BIGINT PRIMARY KEY,
    user_id BIGINT NOT NULL,
    total DECIMAL(12, 2) NOT NULL,
    FOREIGN KEY (user_id) REFERENCES users(id)
);

Giả sử dữ liệu trong bảng users có:

id name
1 An
2 Bình

Bảng orders có:

id user_id total
1001 1 700000
1002 1 300000
1003 2 200000

Hai đơn hàng 1001 và 1002 thuộc về An, còn đơn hàng 1003 thuộc về Bình.

Trong quan hệ One-to-Many, khóa ngoại thường được đặt ở bảng phía Many. Cách thiết kế này cho phép nhiều đơn hàng tham chiếu đến cùng một người dùng mà không cần lặp lại toàn bộ thông tin người dùng trong mỗi đơn hàng.

3.3. Many-to-Many (N) — Nhiều-nhiều

Quan hệ Many-to-Many xảy ra khi nhiều bản ghi ở bảng A có thể liên kết với nhiều bản ghi ở bảng B.

Ví dụ trong hệ thống bán hàng:

  • Một đơn hàng có thể chứa nhiều sản phẩm.
  • Một sản phẩm có thể xuất hiện trong nhiều đơn hàng.

Nếu chỉ đặt product_id trong bảng orders, mỗi đơn hàng sẽ khó biểu diễn nhiều sản phẩm theo cách phù hợp. Nếu đặt order_id trong bảng products, một sản phẩm cũng không thể được liên kết linh hoạt với nhiều đơn hàng bằng một cột duy nhất.

Giải pháp thông thường là tạo bảng trung gian order_items.

CREATE TABLE orders (
    id BIGINT PRIMARY KEY
);

CREATE TABLE products (
    id BIGINT PRIMARY KEY,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(12, 2) NOT NULL
);

CREATE TABLE order_items (
    order_id BIGINT NOT NULL,
    product_id BIGINT NOT NULL,
    quantity INT NOT NULL CHECK (quantity > 0),
    unit_price DECIMAL(12, 2) NOT NULL CHECK (unit_price >= 0),

    PRIMARY KEY (order_id, product_id),

    FOREIGN KEY (order_id) REFERENCES orders(id),
    FOREIGN KEY (product_id) REFERENCES products(id)
);

Trong thiết kế này:

  • order_id tham chiếu đến đơn hàng.
  • product_id tham chiếu đến sản phẩm.
  • quantity lưu số lượng sản phẩm trong đơn hàng.
  • unit_price lưu đơn giá được ghi nhận cho sản phẩm trong đơn hàng.

Khóa chính kép (order_id, product_id) ngăn cùng một sản phẩm xuất hiện nhiều dòng trong cùng đơn hàng theo thiết kế này. Nếu nghiệp vụ cần nhiều dòng riêng cho cùng một sản phẩm, có thể sử dụng một khóa chính riêng cho order_items hoặc thiết kế khóa khác.

Bảng trung gian không chỉ dùng để nối hai bảng mà còn có thể lưu thông tin riêng của mối quan hệ, chẳng hạn số lượng, đơn giá hoặc thời điểm thêm sản phẩm vào đơn hàng.

4. Ví dụ tổng hợp: Thiết kế database cho hệ thống bán hàng

Từ các khái niệm trên, ta có thể xây dựng mô hình dữ liệu đơn giản gồm bốn bảng:

  • users: người dùng.
  • products: sản phẩm.
  • orders: đơn hàng.
  • order_items: các dòng sản phẩm trong đơn hàng.

Các mối quan hệ chính:

  • users 1 orders.
  • orders 1 order_items.
  • products 1 order_items.

Từ đó, quan hệ giữa orders và products là N thông qua bảng trung gian order_items.

Mô hình có thể biểu diễn như sau:

users
  |
  | 1:N
  v
orders
  |
  | 1:N
  v
order_items
  ^
  | N:1
  |
products

Đây là một thiết kế cơ bản nhưng đủ để minh họa cách dữ liệu được chia thành các bảng và liên kết với nhau. Trong một hệ thống thực tế, các bảng có thể cần thêm thông tin như trạng thái đơn hàng, thời điểm tạo, địa chỉ giao hàng và thông tin thanh toán.

5. Database Relationships và SQL JOIN

Sau khi thiết kế các bảng có quan hệ, Backend thường cần lấy dữ liệu từ nhiều bảng cùng lúc. SQL JOIN cho phép kết hợp các bản ghi dựa trên điều kiện liên kết.

Ví dụ, để lấy danh sách đơn hàng kèm tên người đặt:

SELECT
    orders.id AS order_id,
    users.name AS customer_name,
    orders.total
FROM orders
JOIN users ON orders.user_id = users.id;

Điều kiện:

orders.user_id = users.id

xác định đơn hàng thuộc về người dùng nào.

Nếu chỉ truy vấn bảng orders, kết quả có thể chỉ chứa user_id, không có tên người dùng. Khi dùng JOIN, database có thể kết hợp dữ liệu từ hai bảng để trả về kết quả cần thiết.

Một ví dụ khác: lấy các sản phẩm trong một đơn hàng.

SELECT
    order_items.order_id,
    products.name,
    order_items.quantity,
    order_items.unit_price
FROM order_items
JOIN products ON order_items.product_id = products.id
WHERE order_items.order_id = 1001;

Truy vấn này kết hợp dữ liệu từ order_items và products để lấy tên, số lượng và đơn giá của từng sản phẩm thuộc đơn hàng 1001.

JOIN là một trong những lý do quan trọng khiến việc thiết kế quan hệ giữa các bảng cần được thực hiện rõ ràng ngay từ đầu.

6. Database Design trong Backend Go + Gin

Trong Backend, database thường được truy cập thông qua một lớp chuyên trách, thường gọi là Repository hoặc Data Access Layer.

Một kiến trúc phân lớp phổ biến:

Client
  |
  v
Handler
  |
  v
Service
  |
  v
Repository
  |
  v
MySQL / PostgreSQL

Mỗi thành phần có trách nhiệm riêng:

  • Handler: nhận HTTP request, đọc dữ liệu đầu vào và trả HTTP response.
  • Service: thực hiện logic nghiệp vụ, chẳng hạn kiểm tra điều kiện tạo đơn hàng.
  • Repository: thực hiện truy vấn và thao tác dữ liệu.
  • Database: lưu trữ dữ liệu, thực thi SQL và duy trì các ràng buộc được khai báo.

Ví dụ, khi client yêu cầu lấy đơn hàng có ID 1001:

  1. Handler nhận request GET /orders/1001.
  2. Handler gọi Service để lấy thông tin đơn hàng.
  3. Service gọi Repository.
  4. Repository thực hiện truy vấn SQL để lấy dữ liệu từ database.
  5. Kết quả được trả về qua Service và Handler cho client.

Ví dụ truy vấn trong Repository:

SELECT id, user_id, total
FROM orders
WHERE id = $1;

Cú pháp $1 thường dùng với PostgreSQL. Khi sử dụng MySQL, cú pháp placeholder phụ thuộc driver và thư viện, thường là ? với các driver Go phổ biến.

Việc thiết kế bảng và quan hệ ảnh hưởng trực tiếp đến cách Repository viết truy vấn và cách Service phối hợp dữ liệu để thực hiện nghiệp vụ.

7. Kết luận

Database Design & Relationships là nền tảng của việc tổ chức dữ liệu trong các ứng dụng Backend.

Database Design xác định bảng, cột, kiểu dữ liệu, khóa và ràng buộc. Database Relationships xác định cách các bản ghi liên kết với nhau thông qua những quan hệ như 1:1, 1 và N.

Khi hiểu các khái niệm này, Backend Developer có thể thiết kế cấu trúc dữ liệu phù hợp với yêu cầu nghiệp vụ, viết truy vấn SQL chính xác hơn và xây dựng Repository rõ ràng hơn.

Đây cũng là nền tảng để tiếp tục tìm hiểu các chủ đề liên quan như Database Normalization, Transactions & ACID, Database Indexes và N+1 Query Problem.

Nguồn tham khảo

Bài viết khác

ORM

ORM là gì? (Object-Relational Mapping) Trong quá trình phát triển Backend, ứng dụng thường xuyên cần tương tác với database để lưu trữ, truy vấn, cập nhật và xóa dữ liệu. Một cách phổ biến để thực hiện những thao tác này là viết SQL trực tiếp. Tuy nhiên, khi dự án phát triển, việc […]

N+1 Query Problem

N+1 Query Problem là gì? Trong quá trình phát triển Backend, ứng dụng thường cần truy xuất dữ liệu từ database để trả về cho client. Một API có thể cần lấy danh sách đơn hàng cùng thông tin khách hàng tương ứng. Tuy nhiên, nếu ứng dụng truy vấn dữ liệu liên quan riêng […]

Database Indexes

Database Indexes là gì? Trong Backend, database không chỉ cần lưu trữ dữ liệu mà còn phải tìm kiếm và truy xuất dữ liệu hiệu quả. Khi số lượng bản ghi tăng lên, những truy vấn đơn giản như tìm một người dùng theo email hoặc tìm sản phẩm theo tên cũng có thể mất […]

Transactions & ACID

Transactions & ACID là gì? Trong Backend, một request gửi đến API có thể yêu cầu hệ thống thực hiện nhiều thao tác trên database. Chẳng hạn, khi khách hàng đặt hàng, hệ thống có thể cần tạo đơn hàng, lưu chi tiết đơn hàng và cập nhật số lượng tồn kho. Nếu một thao […]

Database Normalization là gì? Tìm hiểu 1NF, 2NF, 3NF và Denormalization

1. Database Normalization là gì? Database Normalization (chuẩn hóa cơ sở dữ liệu) là quá trình tổ chức dữ liệu thành các bảng và thiết lập mối quan hệ phù hợp nhằm hạn chế dữ liệu trùng lặp không cần thiết, đồng thời giảm các vấn đề có thể xảy ra khi thêm, sửa hoặc […]

PostgreSQL và MySQL là gì? So sánh hai hệ quản trị cơ sở dữ liệu phổ biến

1. Tổng quan về cơ sở dữ liệu Khi xây dựng một ứng dụng phần mềm, dữ liệu cần được lưu trữ để có thể truy xuất, cập nhật và quản lý trong suốt quá trình hoạt động. Chẳng hạn, một ứng dụng bán hàng cần lưu thông tin sản phẩm, khách hàng, đơn hàng […]

Leave a Reply

Your email address will not be published. Required fields are marked *