Menu
ESC

Nhập từ khóa để tìm kiếm

↑↓ Di chuyển
Enter Mở
ESC Đóng

Đang tải...

Bài 6 — Primary Keys — Natural, Surrogate, Composite

Data Modeling and ERD Mastery Bài 6/60

Mở đầu — vì sao bài này quan trọng

Hãy tưởng tượng bạn là BA (Business Analyst) cho một ngân hàng. Trong cuộc họp, đội vận hành than phiền: "Có hai khách hàng tên Nguyễn Văn An, cùng ngày sinh, hệ thống cứ trả nhầm lịch sử giao dịch của người này sang người kia." Đó không phải lỗi code. Đó là lỗi mô hình dữ liệu — ai đó đã chọn sai thứ để phân biệt từng khách hàng.

Primary key (khóa chính) chính là câu trả lời cho câu hỏi nền tảng nhất của mọi bảng dữ liệu: "Làm sao để chỉ đích danh một dòng, và chỉ duy nhất một dòng?" Ở Bài 4 và Bài 5, bạn đã học cách xác định entity và attribute. Bài này đi sâu vào phần "trái tim" của mỗi entity — cái định danh duy nhất giúp toàn bộ relationship (sẽ học ở Bài 7) hoạt động được.

Chọn sai primary key là một trong những lỗi tốn kém nhất trong nghề. Nó không gây sập hệ thống ngay lập tức, nhưng âm thầm tạo ra dữ liệu trùng, báo cáo sai, và những dự án migration kéo dài hàng tháng. Một BA giỏi phải đề xuất được khóa chính đúng ngay từ giai đoạn thiết kế, trước khi developer viết một dòng SQL nào.

Khái niệm cốt lõi

Primary key là gì?

Primary key là một cột hoặc một nhóm cột dùng để định danh duy nhất (uniquely identify) mỗi dòng (row) trong một bảng (table). Nó có ba thuộc tính bắt buộc:

  • Unique (duy nhất) — không có hai dòng nào mang cùng một giá trị khóa chính.
  • Not null (không rỗng) — mọi dòng đều phải có giá trị khóa; không được để trống.
  • Stable (ổn định) — giá trị khóa không nên thay đổi theo thời gian. Nếu khóa thay đổi, mọi nơi tham chiếu tới nó đều phải cập nhật theo.
Mỗi bảng chỉ có đúng một primary key, nhưng khóa đó có thể gồm nhiều cột ghép lại. Đây là điểm nhiều người mới hay nhầm: "primary key" không có nghĩa là "một cột", mà là "một định danh".

Phân biệt với candidate key

Trong một bảng có thể có nhiều cột/nhóm cột thỏa mãn tính duy nhất — gọi là candidate key (khóa ứng viên). Ví dụ bảng nhân viên có thể được định danh bằng employee_id, hoặc bằng cccd (số căn cước), hoặc bằng email công ty. Tất cả đều là candidate key. Bạn chọn một trong số đó làm primary key; những cái còn lại trở thành alternate key (thường được đánh dấu UNIQUE).

Ba loại primary key

#### 1. Natural Key (khóa tự nhiên)

Natural key dùng một thuộc tính vốn có ý nghĩa nghiệp vụ trong thế giới thực để làm khóa. Ví dụ: số CCCD của công dân, mã số thuế (MST) của doanh nghiệp, ISBN của sách, biển số xe.

Ưu điểm:

  • Có ý nghĩa, dễ đọc, dễ tra cứu trực tiếp.
  • Không cần sinh thêm cột phụ — tiết kiệm dung lượng.
  • Ràng buộc duy nhất phản ánh đúng quy tắc nghiệp vụ thật.
Nhược điểm:
  • Có thể thay đổi. Số điện thoại, email, thậm chí số CMND (9 số) đổi sang CCCD (12 số) — đều từng xảy ra ở Việt Nam.
  • Có thể bị nhập sai, hoặc chưa có tại thời điểm tạo dòng (em bé mới sinh chưa có CCCD).
  • Đôi khi dài và phức tạp, làm các bảng tham chiếu cồng kềnh.
#### 2. Surrogate Key (khóa thay thế)

Surrogate key là một giá trị được hệ thống sinh ra, không mang ý nghĩa nghiệp vụ — chỉ tồn tại để định danh. Thường là số tự tăng (auto-increment 1, 2, 3...), hoặc UUID (chuỗi ngẫu nhiên kiểu 550e8400-e29b-41d4-a716-446655440000).

Ưu điểm:

  • Không bao giờ thay đổi, không phụ thuộc nghiệp vụ — cực kỳ ổn định.
  • Luôn có sẵn ngay khi tạo dòng.
  • Nhỏ gọn, đồng nhất, tối ưu cho việc join giữa các bảng.
Nhược điểm:
  • Vô nghĩa với con người — nhìn customer_id = 84213 không biết là ai.
  • Không tự bảo vệ chống trùng nghiệp vụ. Hệ thống có thể tạo hai dòng cùng một người thật với hai id khác nhau nếu thiếu ràng buộc UNIQUE bổ sung.
  • UUID có thể gây phân mảnh chỉ mục nếu dùng không khéo (sẽ bàn ở Bài 23).
#### 3. Composite Key (khóa ghép)

Composite key là khóa gồm từ hai cột trở lên ghép lại mới đảm bảo tính duy nhất. Loại này xuất hiện tự nhiên nhất ở các bảng trung gian (junction table) của quan hệ many-to-many.

Ví dụ bảng enrollment (đăng ký học) cần cả student_id lẫn course_id mới xác định được một lượt đăng ký cụ thể. Một mình student_id không đủ (một sinh viên học nhiều môn), một mình course_id cũng không đủ.

Ưu điểm:

  • Diễn đạt đúng quy tắc nghiệp vụ "tổ hợp này là duy nhất".
  • Không cần thêm cột thừa trong nhiều trường hợp.
Nhược điểm:
  • Khi bảng khác cần tham chiếu, foreign key cũng phải ghép nhiều cột — phức tạp.
  • Nếu một thành phần khóa thay đổi, rắc rối lan rộng.

Tình huống thực tế

Tình huống 1 — Techcombank và bài học "số điện thoại làm khóa"

Một đội dự án giả định tại một ngân hàng tầm cỡ Techcombank thiết kế bảng customer cho ứng dụng mobile banking. Ở phiên bản đầu, do số điện thoại là thứ khách dùng để đăng nhập, nhóm developer "tiện tay" lấy luôn phone_number làm primary key.

Mọi thứ ổn trong sáu tháng đầu. Rồi vấn đề ập đến: khoảng 3% khách hàng đổi số điện thoại mỗi năm. Khi một khách đổi số, mọi giao dịch, mọi khoản vay, mọi thẻ liên kết tới số cũ đều "mồ côi". Tệ hơn, nhà mạng tái cấp số điện thoại đã hủy cho người khác — và hệ thống suýt gán lịch sử tài khoản của người cũ cho người mới.

Bài học rút ra: số điện thoại là một natural key tồi vì nó không ổn địnhcó thể bị tái sử dụng. Giải pháp đúng là dùng surrogate key customer_id (số tự tăng) làm primary key, còn phone_number đặt UNIQUE nhưng cho phép cập nhật. Khi khách đổi số, ta chỉ sửa một cột, mọi tham chiếu qua customer_id vẫn nguyên vẹn.

Tình huống 2 — Tiki và bảng giỏ hàng many-to-many

Một sàn thương mại điện tử kiểu Tiki có bảng cart_item lưu các sản phẩm trong giỏ. Ban đầu nhóm thiết kế đặt primary key ghép là (cart_id, product_id) — nghĩa là mỗi sản phẩm chỉ xuất hiện một lần trong một giỏ, và muốn mua 3 cái thì tăng cột quantity.

Composite key này ban đầu hợp lý. Nhưng khi Tiki triển khai tính năng "thêm cùng một sản phẩm với hai ưu đãi khác nhau" (ví dụ một cái dùng voucher freeship, một cái không), giả định "mỗi product chỉ một dòng trong một cart" sụp đổ. Khóa (cart_id, product_id) chặn mất nghiệp vụ mới.

Đội phải migration: thêm surrogate key cart_item_id làm primary key, và biến (cart_id, product_id) thành chỉ mục thường (không còn UNIQUE). Việc này tốn nhiều công vì nhiều bảng khác đã tham chiếu tới khóa ghép cũ.

Bài học rút ra: composite key phản ánh một quy tắc nghiệp vụ tại một thời điểm. Khi nghi ngờ nghiệp vụ có thể thay đổi, hãy ưu tiên surrogate key cho primary, và đặt composite UNIQUE như một ràng buộc riêng — bạn có thể nới lỏng nó sau mà không phá vỡ cấu trúc khóa.

Tình huống 3 — Bệnh viện và natural key đúng chỗ

Một hệ thống quản lý bệnh viện cần bảng province (tỉnh/thành) để chuẩn hóa địa chỉ bệnh nhân. Việt Nam có bộ mã hành chính do Tổng cục Thống kê ban hành: Hà Nội là 01, TP.HCM là 79, Đà Nẵng là 48...

Ở đây, đội BA quyết định dùng chính mã hành chính làm natural primary key cho bảng province, thay vì sinh thêm surrogate.

Vì sao đúng? Vì mã này ổn định (Nhà nước hiếm khi đổi), chuẩn hóa toàn quốc (mọi hệ thống khác cùng dùng nó), và việc giữ natural key giúp tích hợp dữ liệu với cơ quan bảo hiểm y tế dễ dàng — không cần bảng ánh xạ trung gian.

Bài học rút ra: natural key không phải lúc nào cũng xấu. Với các bảng tra cứu (lookup/reference table) có mã chuẩn, ổn định, do bên thứ ba có thẩm quyền ban hành — natural key là lựa chọn gọn gàng và đúng đắn.

Hướng dẫn từng bước

Khi cần chọn primary key cho một entity, hãy đi theo quy trình sau:

Bước 1 — Liệt kê tất cả candidate key. Tự hỏi: "Những cột nào, hoặc tổ hợp cột nào, đảm bảo không bao giờ trùng?" Viết hết ra. Ví dụ với entity nhân viên: cccd, email_công_ty, mã_nhân_viên.

Bước 2 — Kiểm tra ba tiêu chí với từng candidate. Với mỗi ứng viên, hỏi:

  • Có thật sự duy nhất không? (Email cá nhân thì không — hai người có thể dùng chung.)
  • Có bao giờ rỗng tại thời điểm tạo dòng không? (CCCD của trẻ sơ sinh thì có.)
  • Có khả năng thay đổi theo thời gian không? (Email, số điện thoại thì có.)
Bước 3 — Quyết định natural hay surrogate. Nếu có một natural key ổn định, chuẩn, không rỗng (như mã tỉnh) — cân nhắc dùng nó. Nếu tất cả candidate đều có rủi ro thay đổi hoặc có thể rỗng — chọn surrogate key. Mặc định an toàn cho hầu hết bảng giao dịch là surrogate key.

Bước 4 — Bảo toàn tính duy nhất nghiệp vụ. Nếu chọn surrogate, đừng quên đặt UNIQUE cho các natural candidate key quan trọng. Surrogate id chống trùng kỹ thuật, còn UNIQUE trên cccd mới chống trùng nghiệp vụ (một người không bị tạo hai hồ sơ).

Bước 5 — Với bảng many-to-many, cân nhắc composite vs surrogate. Nếu tổ hợp khóa ổn định và sẽ không cần dòng trùng — composite key gọn. Nếu nghi ngờ nghiệp vụ sẽ tiến hóa — dùng surrogate + UNIQUE trên tổ hợp.

Bước 6 — Tài liệu hóa quyết định. Ghi rõ trong data dictionary (Bài 17) lý do chọn khóa, để người sau hiểu và không "sửa nhầm".

Lỗi thường gặp & mẹo

Lỗi 1 — Dùng dữ liệu cá nhân nhạy cảm làm khóa. Lấy CCCD làm primary key rồi rải nó khắp các bảng và URL là rủi ro lộ thông tin (sẽ học sâu ở Bài 47 về PDPL). Mẹo: dùng surrogate key cho định danh, giữ CCCD ở một cột UNIQUE được kiểm soát truy cập.

Lỗi 2 — Tin rằng natural key "chắc chắn không trùng". Tên + ngày sinh nghe như đủ duy nhất, nhưng Việt Nam có hàng nghìn "Nguyễn Văn An" cùng ngày sinh. Mẹo: chỉ coi là khóa khi bạn chứng minh được tính duy nhất bằng quy tắc nghiệp vụ chắc chắn, không phải bằng cảm giác.

Lỗi 3 — Khóa "thông minh" mã hóa nhiều ý nghĩa. Kiểu mã HN-2024-KT-001 (gắn địa điểm, năm, phòng ban) rất hấp dẫn nhưng dễ vỡ: khi nhân viên chuyển phòng, mã phải đổi — mà khóa thì không nên đổi. Mẹo: tách các thông tin đó thành các cột riêng, để khóa luôn "vô nghĩa và bất biến".

Lỗi 4 — Quên NOT NULL hoặc UNIQUE khi nghĩ đã có khóa. Một số đội đặt cột id nhưng quên ràng buộc, dẫn tới trùng âm thầm. Mẹo: primary key trong DBMS tự động áp NOT NULL + UNIQUE; hãy luôn khai báo nó là PRIMARY KEY thật sự, đừng chỉ "ngầm hiểu".

Lỗi 5 — Tái sử dụng giá trị khóa của dòng đã xóa. Nếu xóa khách hàng id = 50 rồi cấp lại 50 cho khách mới, mọi log cũ tham chiếu 50 sẽ trỏ nhầm. Mẹo: không bao giờ tái dùng giá trị surrogate; để số tự tăng chạy tiếp.

Bài tập thực hành

Hãy áp dụng quy trình 6 bước cho ba entity sau. Với mỗi entity: liệt kê candidate key, chọn loại primary key (natural / surrogate / composite) và giải thích vì sao, đồng thời chỉ ra các cột nào cần đặt UNIQUE bổ sung.

  • Entity khách_hàng của một chuỗi cà phê như Highlands, có các thuộc tính: số_điện_thoại, email, tên, điểm_tích_lũy. Khách thường đăng ký bằng số điện thoại tại quầy.
  • Entity chi_tiết_đơn_hàng (order line) trong một đơn hàng e-commerce, lưu từng dòng sản phẩm thuộc một đơn. Có mã_đơn, mã_sản_phẩm, số_lượng, đơn_giá. Lưu ý: cùng một sản phẩm có thể xuất hiện hai dòng nếu áp hai khuyến mãi khác nhau.
  • Entity xã_phường trong bảng tra cứu địa giới hành chính Việt Nam, có mã_hành_chính (chuẩn Tổng cục Thống kê), tên_xã, mã_huyện.
Gợi ý tự kiểm tra: (1) nên dùng surrogate vì số điện thoại và email đều có thể đổi — đặt UNIQUE cho số điện thoại; (2) nên dùng surrogate order_line_id thay vì composite (mã_đơn, mã_sản_phẩm) vì nghiệp vụ cho phép trùng sản phẩm; (3) natural key mã_hành_chính là hợp lý vì ổn định và chuẩn quốc gia.

Tóm tắt

  • Primary key định danh duy nhất mỗi dòng, phải thỏa duy nhất + không rỗng + ổn định. Mỗi bảng có đúng một primary key, có thể gồm nhiều cột.
  • Natural key dùng thuộc tính nghiệp vụ thật — gọn và có ý nghĩa, nhưng rủi ro khi giá trị thay đổi hoặc rỗng. Hợp nhất với bảng tra cứu có mã chuẩn, ổn định.
  • Surrogate key là giá trị vô nghĩa do hệ thống sinh — ổn định tuyệt đối, là lựa chọn mặc định an toàn cho bảng giao dịch. Nhớ đặt UNIQUE cho natural candidate để chống trùng nghiệp vụ.
  • Composite key ghép nhiều cột, tự nhiên ở bảng many-to-many — nhưng dễ vỡ khi nghiệp vụ tiến hóa; cân nhắc surrogate + UNIQUE trên tổ hợp.
  • Quy trình chọn khóa: liệt kê candidate → kiểm tra ba tiêu chí → chọn natural/surrogate → bảo toàn UNIQUE nghiệp vụ → xử lý riêng bảng many-to-many → tài liệu hóa.
Ở Bài 7, ta sẽ thấy primary key chính là nền tảng để xây dựng relationship — vì foreign key ở bảng này thực chất là tham chiếu tới primary key của bảng kia. Chọn khóa chính tốt hôm nay là bạn đang lát đường cho toàn bộ mô hình quan hệ ngày mai.