👋 Chào mừng đến với Nocoem — kiến thức Backend, System Design và lập trình. Khám phá ngay

Database Normalization - Normal Form 1nf 2nf 3nf?

Khám phá quy trình chuẩn hóa cơ sở dữ liệu với các dạng chuẩn 1NF, 2NF và 3NF để tối ưu hóa cấu trúc dữ liệu và tránh dư thừa.
Database Normalization - Normal Form 1nf 2nf 3nf?
Database Normalization - Normal Form 1nf 2nf 3nf?
TL;DR: Chuẩn hóa dữ liệu (database normalization) là cách tổ chức các bảng trong cơ sở dữ liệu quan hệ sao cho mỗi thông tin chỉ nằm đúng một chỗ. Bài này đi qua 3 dạng chuẩn (normal form) cơ bản: 1NF (mỗi ô một giá trị), 2NF (không phụ thuộc một phần vào khóa), 3NF (không phụ thuộc bắc cầu), kèm ví dụ SQL chạy được và phần đánh đổi giữa chuẩn hóa với hiệu năng.

Bạn từng sửa địa chỉ của một khách hàng ở chỗ này, quên sửa ở chỗ kia, rồi hai báo cáo lại cho ra hai địa chỉ khác nhau cho cùng một người chưa? Đó là hậu quả điển hình của một cơ sở dữ liệu chưa được chuẩn hóa. Trong bài này, tôi đi từ khái niệm cơ bản, qua 1NF/2NF/3NF, tới ví dụ SQL cụ thể, để bạn biết khi nào nên tách bảng và khi nào nên dừng lại.

 Minh họa quá trình chuẩn hóa dữ liệu qua các dạng chuẩn 1NF, 2NF, 3NF

Chuẩn hóa dữ liệu (normalization) là gì?

Chuẩn hóa cơ sở dữ liệu (database normalization) là một nguyên tắc thiết kế giúp bạn tổ chức dữ liệu trong các bảng quan hệ một cách có hệ thống và nhất quán. Nói ngắn gọn: mỗi thông tin chỉ nên xuất hiện đúng một lần, ở đúng một bảng.

Mục tiêu chính là tránh sự dư thừa (redundancy) và giữ tính toàn vẹn (integrity) của dữ liệu, đồng thời loại bỏ những rắc rối khi bạn thêm (insert), xóa (delete) hoặc cập nhật (update) dữ liệu, dân trong nghề hay gọi chung nhóm lỗi này là dị thường dữ liệu (data anomaly).

Vì sao cần chuẩn hóa?

Hãy tưởng tượng bạn viết thiệp mời đám cưới cho từng người trong gia đình, và trên mỗi tấm thiệp đều ghi lại địa chỉ nhà bố mẹ. Vài tháng sau bố mẹ chuyển nhà, bạn phải lục lại cả chồng thiệp cũ để sửa từng cái một, chỉ cần quên một tấm là thông tin đã sai lệch. Một cơ sở dữ liệu chưa chuẩn hóa gặp đúng vấn đề đó: cùng một thông tin (địa chỉ, tên nhân viên...) bị lặp lại ở nhiều hàng, chỉ cần sửa thiếu một chỗ là dữ liệu mất nhất quán ngay.

Mục đích chính của chuẩn hóa là tránh sự phức tạp này: loại bỏ trùng lặp và sắp xếp dữ liệu theo cách nhất quán. Trong quá trình chuẩn hóa, dữ liệu được chia thành nhiều bảng nhỏ hơn, liên kết với nhau qua các mối quan hệ (relationship). Quản trị viên cơ sở dữ liệu (DBA) thiết lập các mối quan hệ này bằng khóa chính (primary key), khóa ngoại (foreign key) và khóa tổng hợp (composite key).

Vài khái niệm khóa cần nắm trước

Khóa chính (primary key): cột (hoặc nhóm cột) xác định duy nhất mỗi hàng trong bảng. Một khóa chính luôn phải:

  • Không được NULL
  • Có giá trị duy nhất, không trùng giữa các hàng
  • Hiếm khi thay đổi giá trị sau khi đã gán
  • Được gán giá trị ngay khi thêm một bản ghi mới

Khóa ngoại (foreign key): một trường trong bảng này trỏ tới khóa chính của bảng khác, dùng để liên kết dữ liệu giữa hai bảng.

Khóa tổng hợp (composite key): giống khóa chính, nhưng gồm nhiều cột gộp lại thay vì chỉ một cột.

Cột không khóa (non-key column): những cột còn lại trong bảng, không tham gia vào việc định danh bản ghi.

1NF, 2NF, 3NF là gì?

1NF, 2NF và 3NF là ba dạng chuẩn (normal form) đầu tiên trong chuẩn hóa cơ sở dữ liệu. Ngoài ra còn có BCNF, 4NF, 5NF và 6NF, nhưng trong phạm vi bài này tôi chỉ tập trung vào 3 dạng cơ bản, đủ dùng cho phần lớn thiết kế thực tế.

Các dạng chuẩn mang tính tích lũy: dạng sau luôn kế thừa toàn bộ điều kiện của dạng trước rồi thêm điều kiện mới. Vì vậy một bảng đạt 3NF thì mặc nhiên cũng đang ở 1NF và 2NF.

Dạng chuẩn 1 - 1NF

Một bảng đạt 1NF khi thỏa các tiêu chí sau:

  • Mỗi ô chỉ chứa đúng một giá trị (tính nguyên tử - atomicity)
  • Có khóa chính để định danh bản ghi
  • Không có hàng hoặc cột trùng lặp
  • Mỗi cột chỉ mang một loại giá trị cho mỗi hàng

Ví dụ: giả sử bạn xây một ứng dụng quản lý nhà hàng, cần lưu dữ liệu nhân viên. Bảng nhân viên ban đầu trông như sau:

EMPLOYEE_IDNAMEJOB_CODEJOBSTATE_CODEHOME_STATE
E001AliceJ01Chef26Michigan
E001AliceJ02Waiter26Michigan
E002BobJ02Waiter56Wyoming
E002BobJ03Bartender56Wyoming
E003AliceJ01Chef56Wyoming

Tất cả các ô đều nguyên tử (atomic) và bảng có khóa tổng hợp (employee_id, job_code), nên bảng này đã đạt 1NF.

Nhưng để ý kỹ: chỉ cần biết employee_id, bạn đã suy ra được name, home_state và state_code của người đó (vì chắc chắn là cùng một người). Nghĩa là các cột này chỉ phụ thuộc vào employee_id, tức chỉ phụ thuộc vào một phần của khóa tổng hợp (employee_id, job_code) chứ không phải toàn bộ khóa. Đây gọi là phụ thuộc một phần (partial dependency), và nó khiến bảng chưa đạt 2NF. Để giải quyết, tôi tách bảng ra.

Dạng chuẩn 2 - 2NF

1NF chỉ loại bỏ các nhóm lặp, chứ chưa xử lý được sự dư thừa dữ liệu. Đó là lý do cần thêm 2NF.

Một bảng đạt 2NF khi:

  • Đã đạt 1NF
  • Không có phụ thuộc một phần (partial dependency): mọi thuộc tính không khóa phải phụ thuộc vào toàn bộ khóa chính, không chỉ một phần của nó. (Nếu khóa chính chỉ có một cột, điều kiện này tự động thỏa vì không có "phần" nào để phụ thuộc thiếu.)

Áp dụng vào ví dụ trên, tôi tách bảng nhân viên thành ba bảng:

Bảng employee_roles
EMPLOYEE_IDJOB_CODE
E001J01
E001J02
E002J02
E002J03
E003J01
Bảng employees
EMPLOYEE_IDNAMESTATE_CODEHOME_STATE
E001Alice26Michigan
E002Bob56Wyoming
E003Alice56Wyoming
Bảng jobs
JOB_CODEJOB
J01Chef
J02Waiter
J03Bartender

Đến đây employee_roles và jobs đã ổn, nhưng nhìn kỹ bảng employees: home_state đang phụ thuộc vào state_code, chứ không phụ thuộc trực tiếp vào employee_id. Biết state_code là suy ra được home_state, bất kể employee_id là ai. Đây là một phụ thuộc bắc cầu (transitive dependency) còn sót lại, nên bảng employees vẫn chưa đạt 3NF.

Dạng chuẩn 3 - 3NF

Ở 2NF, các nhóm lặp và phần lớn dư thừa đã được xử lý, nhưng vẫn còn một loại phụ thuộc chưa giải quyết: phụ thuộc bắc cầu (transitive dependency), tức khi một thuộc tính không khóa phụ thuộc vào một thuộc tính không khóa khác, thay vì phụ thuộc trực tiếp vào khóa chính. Đây là điều dạng chuẩn thứ ba (3NF) xử lý.

Một bảng đạt 3NF khi:

  • Đã đạt 2NF
  • Không có phụ thuộc bắc cầu

Tách tiếp bảng employees, tôi tách state_code/home_state ra một bảng states riêng:

Bảng employee_roles
EMPLOYEE_IDJOB_CODE
E001J01
E001J02
E002J02
E002J03
E003J01
Bảng employees
EMPLOYEE_IDNAMESTATE_CODE
E001Alice26
E002Bob56
E003Alice56
Bảng jobs
JOB_CODEJOB
J01Chef
J02Waiter
J03Bartender
Bảng states
STATE_CODEHOME_STATE
26Michigan
56Wyoming

Đến đây, toàn bộ schema đã đạt 3NF: mỗi thông tin (tên nhân viên, tên công việc, tên bang) chỉ nằm ở đúng một bảng.

Ví dụ SQL đầy đủ

Ghép 4 bảng ở trên thành schema SQL chạy được, cùng một truy vấn JOIN để lấy lại thông tin đầy đủ của một nhân viên:

CREATE TABLE states (
  state_code  VARCHAR(5) PRIMARY KEY,
  home_state  VARCHAR(100) NOT NULL
);

CREATE TABLE employees (
  employee_id VARCHAR(10) PRIMARY KEY,
  name        VARCHAR(100) NOT NULL,
  state_code  VARCHAR(5) NOT NULL REFERENCES states(state_code)
);

CREATE TABLE jobs (
  job_code VARCHAR(10) PRIMARY KEY,
  job      VARCHAR(50) NOT NULL
);

CREATE TABLE employee_roles (
  employee_id VARCHAR(10) REFERENCES employees(employee_id),
  job_code    VARCHAR(10) REFERENCES jobs(job_code),
  PRIMARY KEY (employee_id, job_code)
);

-- Muốn xem đầy đủ thông tin nhân viên kèm công việc và bang cư trú,
-- join lại 4 bảng thay vì đọc từ một bảng phẳng duy nhất:
SELECT e.employee_id, e.name, j.job, s.home_state
FROM employees e
JOIN employee_roles r ON r.employee_id = e.employee_id
JOIN jobs j ON j.job_code = r.job_code
JOIN states s ON s.state_code = e.state_code
WHERE e.employee_id = 'E001';
employee_id | name | job | home_state E001 | Alice | Chef | Michigan E001 | Alice | Waiter | Michigan

Đổi lại, nếu sau này bạn cần sửa tên bang, chỉ cần UPDATE đúng một dòng trong bảng states, thay vì phải rà từng dòng trong một bảng phẳng duy nhất như ban đầu.

Ưu điểm của chuẩn hóa

  • Nhất quán dữ liệu hơn (consistency): mỗi thông tin chỉ lưu ở một nơi, sửa một chỗ là toàn hệ thống cập nhật theo.
  • Giảm dư thừa (redundancy): tách dữ liệu thành nhiều bảng liên quan giúp tiết kiệm dung lượng lưu trữ và tránh lặp dữ liệu.
  • Truy vấn rõ ràng hơn: dữ liệu được sắp xếp hợp lý nên dễ viết đúng truy vấn và tối ưu nó.
  • Dữ liệu có ý nghĩa hơn: nhóm dữ liệu theo đúng bản chất giúp schema dễ hiểu hơn, kể cả với người không trực tiếp thiết kế nó.
  • Giảm dị thường (anomaly): ít rủi ro sai lệch khi thêm, sửa, xóa dữ liệu.

Cạm bẫy và khi nào nên cân nhắc phi chuẩn hóa

  • Độ phức tạp tăng: nhiều bảng đồng nghĩa nhiều khóa ngoại và mối quan hệ hơn, dễ rối nếu không kiểm soát tốt.
  • Nhiều JOIN hơn: càng chuẩn hóa sâu, truy vấn càng cần join nhiều bảng, có thể ảnh hưởng hiệu năng đọc.
  • Mất ngữ cảnh trực quan: dữ liệu bị chia nhỏ nên đôi khi phải xem qua nhiều bảng mới hiểu hết một bản ghi.
  • Đòi hỏi hiểu đúng mối quan hệ dữ liệu: chuẩn hóa sai (xác định nhầm phụ thuộc) còn tệ hơn không chuẩn hóa.

Trong thực tế, không phải lúc nào cũng nên chuẩn hóa tới 3NF hay xa hơn. Với hệ thống đọc nhiều, ghi ít (như báo cáo, phân tích), người ta thường cố ý phi chuẩn hóa (denormalization) một phần: chấp nhận trùng lặp dữ liệu để đổi lấy tốc độ đọc, rồi xử lý tính nhất quán ở tầng ứng dụng. Tôi có bàn kỹ hơn về đánh đổi này ở bài "Backend Performance - Database (phi chuẩn hóa)" trong mục Bài liên quan bên dưới.

Tóm tắt

  • Chuẩn hóa giúp mỗi thông tin chỉ nằm ở đúng một chỗ, giảm dư thừa và dị thường dữ liệu.
  • 1NF: mỗi ô một giá trị, có khóa chính, không hàng/cột trùng lặp.
  • 2NF: đạt 1NF, và không có thuộc tính chỉ phụ thuộc một phần khóa tổng hợp.
  • 3NF: đạt 2NF, và không có thuộc tính không khóa phụ thuộc bắc cầu qua một thuộc tính không khóa khác.
  • Chuẩn hóa không miễn phí: đổi lấy sự nhất quán, bạn chấp nhận nhiều bảng và nhiều JOIN hơn, đôi khi phi chuẩn hóa có chủ đích lại là lựa chọn đúng.

Nguồn tham khảo

  • E.F. Codd, "A Relational Model of Data for Large Shared Data Banks", Communications of the ACM, 1970 - bài báo đặt nền móng cho mô hình quan hệ và khái niệm 1NF.
  • E.F. Codd, "Further Normalization of the Data Base Relational Model", IBM Research Report, 1971 - giới thiệu 2NF và 3NF.

- Kai

Thấy bài viết hữu ích?

0 lượt thích

Đăng nhận xét