Chuyển tới nội dung chính

Database Normalization (1NF, 2NF, 3NF)

Bạn sẽ học được gì

Đến cuối trang này, bạn sẽ có thể:

  • Giải thích tại sao chuẩn hóa (Normalization) quan trọng — nó giải quyết những vấn đề gì
  • Định nghĩa 1NF, 2NF, và 3NF với các yêu cầu chính xác
  • Chuyển đổi một bảng chưa chuẩn hóa (Unnormalized Table) từng bước qua mỗi dạng chuẩn
  • Nhận diện phụ thuộc hàm (Functional Dependencies)phụ thuộc bắc cầu (Transitive Dependencies)
  • Biết khi nào nên phản chuẩn hóa (Denormalize) để tối ưu hiệu suất

Vấn đề: Dữ liệu dư thừa và không nhất quán

Hãy tưởng tượng một bảng duy nhất lưu trữ mọi thứ về đơn hàng:

order_idcustomer_namecustomer_emailproduct_namesquantitiesprices
1Alicealice@mail.comLaptop, Mouse1, 2999, 25
2Alicealice@mail.comKeyboard179

Các vấn đề phát sinh:

  • Bất thường cập nhật (Update Anomaly) — Email của Alice bị trùng lặp. Cập nhật ở một hàng nhưng không cập nhật ở hàng khác sẽ gây mất nhất quán.
  • Bất thường thêm (Insert Anomaly) — Không thể thêm một khách hàng mới chưa từng đặt hàng.
  • Bất thường xóa (Delete Anomaly) — Xóa đơn hàng 2 sẽ xóa bản ghi duy nhất của Alice, mất hoàn toàn thông tin liên hệ.

Chuẩn hóa (Normalization) là quá trình cấu trúc cơ sở dữ liệu để loại bỏ các bất thường này bằng cách tách bảng và thiết lập các mối quan hệ.

Các khái niệm cốt lõi

Phụ thuộc hàm (Functional Dependency)

Một phụ thuộc hàm tồn tại khi giá trị của một thuộc tính xác định giá trị của một thuộc tính khác.

  • Ký hiệu: A → B nghĩa là "A xác định B"
  • Ví dụ: student_id → student_name (biết mã sinh viên sẽ xác định duy nhất tên sinh viên)

Phụ thuộc một phần (Partial Dependency)

Một phụ thuộc một phần xảy ra khi một thuộc tính không khóa phụ thuộc vào chỉ một phần của khóa chính tổng hợp (Composite Primary Key), không phải toàn bộ khóa.

Phụ thuộc bắc cầu (Transitive Dependency)

Một phụ thuộc bắc cầu xảy ra khi A → BB → C, nghĩa là A → C gián tiếp. Thuộc tính không khóa C phụ thuộc vào một thuộc tính không khóa khác B thay vì trực tiếp vào khóa chính.


Dạng chuẩn thứ nhất (First Normal Form — 1NF)

Quy tắc

  1. Giá trị nguyên tử (Atomic Values) — mỗi ô chứa một giá trị đơn lẻ, không thể chia nhỏ (không có danh sách, mảng, hoặc giá trị phân cách bằng dấu phẩy)
  2. Không có nhóm lặp (No Repeating Groups) — mỗi cột có tên duy nhất; không có cột trùng lặp như phone1, phone2
  3. Hàng duy nhất (Unique Rows) — mỗi hàng có thể phân biệt được (thường thông qua khóa chính)

Trước 1NF (vi phạm)

order_idcustomer_nameproducts
1AliceLaptop, Mouse, Keyboard
2BobMonitor

Vi phạm: products chứa nhiều giá trị trong một ô duy nhất.

Sau 1NF

order_idcustomer_nameproduct
1AliceLaptop
1AliceMouse
1AliceKeyboard
2BobMonitor

Bây giờ mỗi ô đều là giá trị nguyên tử. Nhưng chúng ta đã tạo ra một vấn đề mới: customer_name lặp lại cho mỗi sản phẩm trong cùng một đơn hàng — đó là sự dư thừa, sẽ được giải quyết bởi 2NF.


Dạng chuẩn thứ hai (Second Normal Form — 2NF)

Quy tắc

  1. Phải đạt 1NF
  2. Không có phụ thuộc một phần (No Partial Dependencies) — 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 phải chỉ một phần

2NF chỉ áp dụng cho các bảng có khóa chính tổng hợp (Composite Primary Key). Nếu bảng của bạn có khóa chính là một cột duy nhất, nó tự động đạt 2NF (miễn là đã đạt 1NF).

Trước 2NF (vi phạm)

Xét một bảng theo dõi sinh viên tham gia khóa học nào, với giảng viên cho mỗi khóa:

student_idcourse_idstudent_namecourse_nameinstructor
1CS101AliceIntro to CSProf. Smith
1MATH200AliceCalculus IIProf. Jones
2CS101BobIntro to CSProf. Smith

Khóa chính tổng hợp: (student_id, course_id)

Các phụ thuộc:

  • (student_id, course_id) → instructor ✅ phụ thuộc đầy đủ
  • student_id → student_namephụ thuộc một phần — phụ thuộc vào chỉ một phần của khóa
  • course_id → course_name, instructorphụ thuộc một phần — phụ thuộc vào chỉ một phần của khóa

Sau 2NF — Tách thành ba bảng

enrollments (bảng liên kết — Junction Table):

student_idcourse_id
1CS101
1MATH200
2CS101

students:

student_idstudent_name
1Alice
2Bob

courses:

course_idcourse_nameinstructor
CS101Intro to CSProf. Smith
MATH200Calculus IIProf. Jones

Bây giờ mọi thuộc tính không khóa đều phụ thuộc vào toàn bộ khóa chính của bảng chứa nó.


Dạng chuẩn thứ ba (Third Normal Form — 3NF)

Quy tắc

  1. Phải đạt 2NF
  2. Không có phụ thuộc bắc cầu (No Transitive Dependencies) — các thuộc tính không khóa phải phụ thuộc vào khóa chính trực tiếp, không thông qua một thuộc tính không khóa khác

Trước 3NF (vi phạm)

emp_idemp_namedept_iddept_namedept_location
1AliceD01EngineeringFloor 3
2BobD01EngineeringFloor 3
3CarolD02MarketingFloor 1

Các phụ thuộc:

  • emp_id → emp_name, dept_id, dept_name, dept_location
  • dept_id → dept_name, dept_locationphụ thuộc bắc cầu
  • Nghĩa là: emp_id → dept_id → dept_name, dept_locationdept_namedept_location phụ thuộc vào dept_id, không trực tiếp vào emp_id

Điều này gây ra: nếu Engineering chuyển sang Floor 5, bạn phải cập nhật mọi nhân viên trong phòng ban đó — bất thường cập nhật (Update Anomaly).

Sau 3NF — Tách thành hai bảng

employees:

emp_idemp_namedept_id
1AliceD01
2BobD01
3CarolD02

departments:

dept_iddept_namedept_location
D01EngineeringFloor 3
D02MarketingFloor 1

Bây giờ dept_namedept_location chỉ phụ thuộc vào dept_id (khóa chính của bảng departments), không phụ thuộc bắc cầu qua emp_id.


Tóm tắt: 1NF → 2NF → 3NF

Dạng chuẩnYêu cầuLoại bỏ
1NFGiá trị nguyên tử, không có nhóm lặpÔ đa giá trị, cột trùng lặp
2NF1NF + không có phụ thuộc một phầnDư thừa từ tập con của khóa tổng hợp
3NF2NF + không có phụ thuộc bắc cầuDư thừa từ phụ thuộc không khóa → không khóa

Cách kiểm tra nhanh cho mỗi dạng chuẩn:

  • 1NF: Mỗi ô có phải là một giá trị duy nhất không? Có nhóm lặp nào không?
  • 2NF: Mọi cột không khóa có phụ thuộc vào toàn bộ khóa chính không? (Chỉ liên quan đến khóa tổng hợp)
  • 3NF: Mọi cột không khóa có phụ thuộc vào khóa chính trực tiếp không, không thông qua cột không khóa khác?

Ví dụ đầy đủ: Chuyển đổi từng bước

Bảng chưa chuẩn hóa (Unnormalized)

order_iddatecustomer_idcustomer_namecustomer_cityproduct_idproduct_namequantityunit_price
10012024-01-15C50AliceHanoiP01Laptop1999
10012024-01-15C50AliceHanoiP02Mouse225
10022024-01-16C51BobSaigonP01Laptop1999

Sau 1NF — đã nguyên tử ✅

Mỗi ô là một giá trị duy nhất. Không có nhóm lặp.

Sau 2NF — kiểm tra phụ thuộc một phần

Khóa chính: (order_id, product_id) — khóa tổng hợp.

  • order_id → date, customer_id, customer_name, customer_city ❌ phụ thuộc một phần — tách ra orders
  • product_id → product_name, unit_price ❌ phụ thuộc một phần — tách ra products
  • (order_id, product_id) → quantity ✅ phụ thuộc đầy đủ — giữ trong order_items

Kết quả: orders, order_items, products

Sau 3NF — kiểm tra phụ thuộc bắc cầu

Trong bảng orders: customer_id → customer_name, customer_citycustomer_namecustomer_city phụ thuộc vào customer_id, không trực tiếp vào order_id. Đây là phụ thuộc bắc cầu.

Kết quả: tách orders thành orderscustomers.

Schema đã chuẩn hóa hoàn chỉnh (3NF)

customers:

customer_idcustomer_namecustomer_city
C50AliceHanoi
C51BobSaigon

orders:

order_idorder_datecustomer_id
10012024-01-15C50
10022024-01-16C51

products:

product_idproduct_nameunit_price
P01Laptop999
P02Mouse25

order_items:

order_idproduct_idquantity
1001P011
1001P022
1002P011

Khi nào nên phản chuẩn hóa (Denormalize)

Schema đã chuẩn hóa giảm thiểu dư thừa nhưng có thể làm tăng độ phức tạp JOINđộ trễ truy vấn (Query Latency). Trong thực tế, phản chuẩn hóa thường được sử dụng cho:

Tình huốngCách xử lý
Hệ thống đọc nhiều (Read-Heavy)Nhân đôi các cột thường xuyên truy cập để tránh JOINs
Báo cáo / OLAPTổng hợp trước dữ liệu vào bảng tóm tắt hoặc Materialized Views
JOIN có độ trễ caoNhúng dữ liệu liên quan (ví dụ: lưu customer_name trong orders để hiển thị nhanh)
Cơ sở dữ liệu phân tán (Distributed Databases)Đặt cùng vị trí dữ liệu liên quan để giảm truy vấn xuyên节点
mẹo

Chuẩn hóa trước, phản chuẩn hóa có chủ đích. Bắt đầu với 3NF làm cơ sở. Chỉ phản chuẩn hóa khi bạn có một vấn đề hiệu suất có thể đo lường được và lợi ích rõ ràng.

Các sai lầm thường gặp

  • Dừng lại ở 1NF — ô đa giá trị "hoạt động" ban đầu nhưng gây khó khăn khi phân tích và ngăn việc lập chỉ mục (Indexing)
  • Chuẩn hóa quá mức (Over-normalizing) — tách thành quá nhiều bảng nhỏ làm truy vấn trở nên phức tạp với quá nhiều JOINs
  • Quên lập chỉ mục khóa ngoại (Foreign Key) — schema đã chuẩn hóa phụ thuộc nhiều vào JOINs; thiếu index trên khóa ngoại sẽ làm giảm hiệu suất nghiêm trọng
  • Phản chuẩn hóa quá sớm — phản chuẩn hóa trước khi cần sẽ tạo ra chính các bất thường mà chuẩn hóa được thiết kế để loại bỏ
  • Bỏ qua quy tắc nghiệp vụ — chuẩn hóa là công cụ cấu trúc; các quy tắc nghiệp vụ phức tạp vẫn có thể cần xác thực ở cấp ứng dụng

Câu hỏi phỏng vấn

1. Chuẩn hóa cơ sở dữ liệu (Database Normalization) là gì và tại sao nó quan trọng?

Chuẩn hóa là quá trình tổ chức cơ sở dữ liệu thành các bảng và cột để giảm thiểu dư thừaloại bỏ các bất thường dữ liệu (Data Anomalies) (thêm, cập nhật, xóa). Nó đảm bảo mỗi phần dữ liệu được lưu trữ ở đúng một nơi, giúp cơ sở dữ liệu dễ bảo trì và nhất quán hơn.

2. Giải thích 1NF, 2NF, và 3NF một cách đơn giản.

Dạng chuẩnDiễn giải đơn giản
1NFMột giá trị trên mỗi ô. Không có danh sách hoặc giá trị phân cách bằng dấu phẩy trong cột.
2NFMọi cột không khóa phụ thuộc vào toàn bộ khóa chính. (Không có phụ thuộc một phần.)
3NFMọi cột không khóa phụ thuộc chỉ vào khóa chính. (Không có phụ thuộc bắc cầu.)

Một câu nhớ: "Mọi thuộc tính không khóa phải cung cấp thông tin về khóa (1NF), toàn bộ khóa (2NF), và chỉ về khóa mà thôi (3NF)."

3. Sự khác biệt giữa phụ thuộc một phần (Partial Dependency) và phụ thuộc bắc cầu (Transitive Dependency) là gì?

  • Phụ thuộc một phần (Partial Dependency): một thuộc tính không khóa phụ thuộc vào một phần của khóa tổng hợp (ví dụ: student_name chỉ phụ thuộc vào student_id, không phải (student_id, course_id)). Chỉ liên quan đến khóa chính tổng hợp.
  • Phụ thuộc bắc cầu (Transitive Dependency): 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 (ví dụ: dept_name phụ thuộc vào dept_id, mà dept_id phụ thuộc vào emp_id). Có thể tồn tại ngay cả với khóa chính là một cột duy nhất.

4. Một bảng có khóa chính là một cột duy nhất có tự động đạt 2NF không?

Có. 2NF yêu cầu loại bỏ các phụ thuộc một phần — khi một cột không khóa chỉ phụ thuộc vào một phần của khóa chính. Nếu khóa chính là một cột duy nhất, không có "phần của nó" để phụ thuộc một phần, nên không thể tồn tại phụ thuộc một phần.

5. Khi nào bạn nên phản chuẩn hóa (Denormalize) cơ sở dữ liệu?

Phản chuẩn hóa khi hiệu suất truy vấn quan trọng hơn hiệu quả lưu trữ:

  • Hệ thống đọc nhiều nơi JOINs là điểm nghẽn
  • Dashboard báo cáo và phân tích cần dữ liệu được tổng hợp trước
  • Kiến trúc CQRS nơi mô hình đọc (Read Model) tách biệt với mô hình ghi (Write Model)
  • Cơ sở dữ liệu phân tán (Distributed Databases) nơi JOINs xuyên node rất tốn kém

Luôn chuẩn hóa trước, sau đó phản chuẩn hóa với mục tiêu hiệu suất cụ thể.

6. Các bất thường (Anomalies) nào mà chuẩn hóa ngăn ngừa?

Bất thườngMô tảVí dụ
Bất thường thêm (Insert Anomaly)Không thể thêm bản ghi mà không có dữ liệu không liên quanKhông thể thêm phòng ban mới cho đến khi có nhân viên được phân công
Bất thường cập nhật (Update Anomaly)Cập nhật một giá trị yêu cầu cập nhật nhiều hàngThay đổi email khách hàng yêu cầu cập nhật mọi hàng đơn hàng
Bất thường xóa (Delete Anomaly)Xóa một bản ghi vô tình làm mất dữ liệu khácXóa đơn hàng cuối cùng của khách hàng sẽ xóa hoàn toàn thông tin khách hàng

Tìm hiểu thêm

  • Primary Key vs Foreign Key — cách khóa thiết lập mối quan hệ giữa các bảng đã chuẩn hóa
  • ACID Properties — cách giao dịch (Transactions) đảm bảo tính nhất quán trong cơ sở dữ liệu đã chuẩn hóa
  • SQL Joins — truy vấn qua các bảng đã chuẩn hóa
  • Database Indexing — tăng tốc JOINs giữa các bảng đã chuẩn hóa