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

Primary Key vs Foreign Key (Khóa chính vs Khóa ngoại)

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

Kết thúc trang này, bạn sẽ có thể:

  • Giải thích Khóa chính (Primary Key) là gì và tại sao mọi bảng đều cần một
  • Giải thích Khóa ngoại (Foreign Key) là gì và cách nó thiết lập mối quan hệ giữa các bảng
  • So sánh Khóa chính và Khóa ngoại — mục đích, ràng buộc (Constraint), và hành vi của chúng
  • Hiểu Tính toàn vẹn tham chiếu (Referential Integrity) và cách Khóa ngoại thực thi nó
  • Chọn giữa Khóa tự nhiên (Natural Key)Khóa thay thế (Surrogate Key) với các đánh đổi
  • Áp dụng các Best Practices khi định nghĩa khóa trong schema thực tế

Vấn đề: Nhận diện và Liên kết Dữ liệu

Hãy tưởng tượng một cơ sở dữ liệu cho nền tảng thương mại điện tử. Bạn có hàng triệu đơn hàng, hàng nghìn sản phẩm, và hàng nghìn người dùng. Hai câu hỏi cơ bản nảy sinh:

  1. Làm sao để nhận diện duy nhất mỗi dòng dữ liệu? — Không có một định danh đáng tin cậy, bạn không thể phân biệt đơn hàng này với đơn hàng khác, người dùng này với người dùng khác.
  2. Làm sao để liên kết dữ liệu liên quan giữa các bảng? — Một đơn hàng thuộc về một người dùng và chứa các sản phẩm. Không có mối quan hệ rõ ràng, dữ liệu bị đứt gãy và không đáng tin cậy.

Khóa chính (Primary Key) giải quyết vấn đề đầu tiên. Khóa ngoại (Foreign Key) giải quyết vấn đề thứ hai. Cùng nhau, chúng là xương sống của thiết kế cơ sở dữ liệu quan hệ.

Khóa chính (Primary Key)

Khóa chính (Primary Key) là một cột (hoặc tập hợp các cột) nhận diện duy nhất mỗi dòng trong một bảng. Mọi bảng trong cơ sở dữ liệu quan hệ nên có chính xác một khóa chính.

Thuộc tính

Thuộc tínhMô tả
Duy nhất (Unique)Không có hai dòng nào có cùng giá trị khóa chính
Không NULL (Not NULL)Các cột khóa chính không thể chứa giá trị NULL
Bất biến (Immutable)Giá trị khóa chính không nên thay đổi theo thời gian
Chỉ một mỗi bảngMột bảng chỉ có thể có một khóa chính (dù có thể là khóa tổng hợp - Composite Key)

Tạo Khóa chính

-- Khóa chính một cột
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255)
);

-- Ràng buộc có tên (khuyến nghị cho production)
CREATE TABLE users (
id INT,
name VARCHAR(100),
email VARCHAR(255),
CONSTRAINT pk_users PRIMARY KEY (id)
);

-- Khóa chính tổng hợp (Composite Primary Key - nhiều cột)
CREATE TABLE order_items (
order_id INT,
product_id INT,
quantity INT,
PRIMARY KEY (order_id, product_id)
);

Điều gì tạo nên một Khóa chính tốt?

Tiêu chíTại sao quan trọng
Duy nhất (Unique)Không bao giờ trùng lặp — đảm bảo mọi dòng đều có thể nhận diện
Không bao giờ NULLNULL không thể so sánh, nên không thể dùng làm định danh đáng tin cậy
Bất biến (Immutable)Thay đổi khóa chính yêu cầu cập nhật tất cả khóa ngoại tham chiếu đến nó
Đơn giản (Simple)Ưu tiên một cột số nguyên thay vì khóa tổng hợp nhiều cột
Hiệu quả (Efficient)Kiểu dữ liệu nhỏ (INT, BIGINT) đánh chỉ mục và nối (Join) nhanh hơn chuỗi hoặc UUID

Khóa ngoại (Foreign Key)

Khóa ngoại (Foreign Key) là một cột (hoặc tập hợp các cột) trong một bảng tham chiếu đến khóa chính của bảng khác. Nó thiết lập mối quan hệ giữa hai bảng và thực thi Tính toàn vẹn tham chiếu (Referential Integrity).

Thuộc tính

Thuộc tínhMô tả
Tham chiếu khóa chínhKhóa ngoại trỏ đến khóa chính (hoặc khóa duy nhất) trong bảng khác
Có thể NULLKhóa ngoại có thể NULL, nghĩa là mối quan hệ là tùy chọn
Có thể trùng lặpNhiều dòng có thể tham chiếu đến cùng một dòng cha (Một-nhiều - One-to-Many)
Nhiều mỗi bảngMột bảng có thể có nhiều khóa ngoại tham chiếu đến các bảng khác nhau

Tạo Khóa ngoại

CREATE TABLE orders (
id INT PRIMARY KEY,
user_id INT,
order_date DATE,
status VARCHAR(20),
CONSTRAINT fk_orders_users
FOREIGN KEY (user_id)
REFERENCES users(id)
);

Tính toàn vẹn Tham chiếu (Referential Integrity)

Tính toàn vẹn tham chiếu đảm bảo rằng mọi giá trị khóa ngoại khớp với một khóa chính tồn tại trong bảng được tham chiếu — không có tham chiếu mồ côi (Orphaned Reference).

  • Đơn hàng 101, 102, và 103 hợp lệ — user_id tham chiếu đến người dùng thực.
  • Đơn hàng 104 không hợp lệuser_id = 999 không tồn tại trong users. Ràng buộc khóa ngoại sẽ từ chối lệnh insert này.

Hành động ON DELETE / ON UPDATE

Khi một dòng được tham chiếu bị xóa hoặc cập nhật, cơ sở dữ liệu phải quyết định điều gì xảy ra với các dòng tham chiếu đến nó. Khóa ngoại hỗ trợ hành động tham chiếu (Referential Actions):

CONSTRAINT fk_orders_users
FOREIGN KEY (user_id)
REFERENCES users(id)
ON DELETE CASCADE -- xóa đơn hàng khi người dùng bị xóa
ON UPDATE CASCADE; -- cập nhật user_id khi users.id thay đổi
Hành độngON DELETEON UPDATE
CASCADETự động xóa các dòng tham chiếuTự động cập nhật các dòng tham chiếu
SET NULLĐặt khóa ngoại thành NULLĐặt khóa ngoại thành NULL
SET DEFAULTĐặt khóa ngoại về giá trị mặc địnhĐặt khóa ngoại về giá trị mặc định
RESTRICTNgăn chặn xóa (lỗi)Ngăn chặn cập nhật (lỗi)
NO ACTIONGiống RESTRICT (trong một số DB có thể trì hoãn)Giống RESTRICT (trong một số DB có thể trì hoãn)
mẹo

RESTRICT (hoặc NO ACTION) là mặc định an toàn nhất — nó buộc ứng dụng xử lý mối quan hệ một cách rõ ràng. Chỉ sử dụng CASCADE khi bạn chắc chắn rằng xóa cha luôn nên xóa con (ví dụ: chi tiết đơn hàng khi đơn hàng bị xóa).

Khóa chính vs Khóa ngoại: So sánh

Khóa chính (Primary Key)Khóa ngoại (Foreign Key)
Mục đíchNhận diện duy nhất mỗi dòng trong bảngTham chiếu đến khóa chính trong bảng khác
Tính duy nhấtPhải duy nhấtCó thể có giá trị trùng lặp
NULLKhông thể NULLCó thể NULL (mối quan hệ tùy chọn)
Mỗi bảngChính xác mộtKhông hoặc nhiều
HướngĐược tham chiếu bởi khóa ngoạiTham chiếu đến khóa chính
Tự tạoThường tự tăng (Auto-increment) hoặc UUIDGiá trị đến từ bảng được tham chiếu
Chỉ mục (Index)Tự động được đánh chỉ mục (Clustered trong hầu hết DB)Không tự động đánh chỉ mục trong một số DB (ví dụ: PostgreSQL, MySQL InnoDB)
Loại ràng buộcTính toàn vẹn thực thể (Entity Integrity)Tính toàn vẹn tham chiếu (Referential Integrity)

Bản số của Mối quan hệ (Relationship Cardinality)

Khóa ngoại định nghĩa bản số (Cardinality) của mối quan hệ giữa các bảng:

Mối quan hệVí dụCách hoạt động
Một-Nhiều (One-to-Many)Một người dùng → nhiều đơn hàngKhóa ngoại ở phía "nhiều" (orders.user_id)
Một-Một (One-to-One)Một người dùng → một hồ sơKhóa ngoại ở một trong hai phía với ràng buộc UNIQUE
Nhiều-Nhiều (Many-to-Many)Sinh viên ↔ khóa họcBảng nối (Junction Table) với hai khóa ngoại
-- Một-Một: hồ sơ người dùng
CREATE TABLE profiles (
id INT PRIMARY KEY,
user_id INT UNIQUE, -- UNIQUE biến nó thành 1:1
bio TEXT,
CONSTRAINT fk_profiles_users FOREIGN KEY (user_id) REFERENCES users(id)
);

-- Nhiều-Nhiều: sinh viên ↔ khóa học (bảng nối - Junction Table)
CREATE TABLE enrollments (
student_id INT,
course_id INT,
enrolled_at TIMESTAMP,
PRIMARY KEY (student_id, course_id),
FOREIGN KEY (student_id) REFERENCES students(id),
FOREIGN KEY (course_id) REFERENCES courses(id)
);

Khóa tự nhiên (Natural Key) vs Khóa thay thế (Surrogate Key)

Khi chọn khóa chính, bạn đối mặt với một quyết định thiết kế cơ bản:

Khóa tự nhiên (Natural Key)Khóa thay thế (Surrogate Key)
Định nghĩaCột có ý nghĩa nghiệp vụ (ví dụ: email, ISBN, SSN)Cột được tạo nhân tạo không có ý nghĩa nghiệp vụ (ví dụ: INT tự tăng, UUID)
Ví dụemail, isbn, tax_idid INT AUTO_INCREMENT, id UUID
Ưu điểmKhông cần cột phụ; có ý nghĩa trực tiếpKhông bao giờ thay đổi; đơn giản; đồng nhất giữa các bảng
Nhược điểmCó thể thay đổi (email, SSN); có thể là tổng hợp; có thể lớnCột phụ; không có ý nghĩa nghiệp vụ
Hiệu năngCó thể chậm nếu khóa là chuỗi dàiNối (Join) nhanh trên kiểu số nguyên nhỏ

Khóa thay thế (khuyến nghị)

-- Số nguyên tự tăng (phổ biến nhất)
CREATE TABLE users (
id INT GENERATED ALWAYS AS IDENTITY PRIMARY KEY, -- PostgreSQL 10+
name VARCHAR(100),
email VARCHAR(255) UNIQUE
);

-- SQL Server
CREATE TABLE users (
id INT IDENTITY(1,1) PRIMARY KEY,
name VARCHAR(100),
email VARCHAR(255) UNIQUE
);

-- UUID (cho hệ thống phân tán - Distributed Systems)
CREATE TABLE users (
id UUID DEFAULT gen_random_uuid() PRIMARY KEY, -- PostgreSQL 13+
name VARCHAR(100),
email VARCHAR(255) UNIQUE
);

Khi nào nên sử dụng Khóa tự nhiên

Khóa tự nhiên phù hợp khi định danh nghiệp vụ thực sự bất biếnđảm bảo duy nhất:

  • Mã quốc gia ISO (US, VN, DE)
  • Mã tiền tệ (USD, EUR)
  • Mã ngôn ngữ (en, vi)
cảnh báo

Tránh sử dụng email, số điện thoại, hoặc SSN làm khóa chính. Chúng có thể thay đổi, được sử dụng lại, hoặc có vấn đề về quyền riêng tư. Sử dụng khóa thay thế và đặt ràng buộc UNIQUE trên định danh tự nhiên.

Đánh chỉ mục Khóa ngoại (Foreign Key Indexing)

Một yếu tố quan trọng về hiệu năng: các cột khóa ngoại nên được đánh chỉ mục (Indexed).

Nếu không có index trên orders.user_id, cơ sở dữ liệu phải thực hiện quét toàn bảng (Full Table Scan) trên orders mỗi khi một dòng trong users bị xóa hoặc cập nhật (để thực thi ràng buộc khóa ngoại). Điều này có thể ảnh hưởng nghiêm trọng đến hiệu năng.

-- PostgreSQL, MySQL InnoDB: khóa ngoại KHÔNG được tự động đánh chỉ mục ở phía tham chiếu
-- Bạn phải tạo index thủ công:
CREATE INDEX idx_orders_user_id ON orders(user_id);

-- SQL Server: khóa ngoại CŨNG KHÔNG được tự động đánh chỉ mục
-- Cùng quy tắc áp dụng — luôn đánh chỉ mục các cột khóa ngoại của bạn.
mẹo

Luôn đánh chỉ mục các cột khóa ngoại. Đây là một trong những lỗi hiệu năng phổ biến nhất trong thiết kế cơ sở dữ liệu. Một số ORM và framework không làm điều này tự động.

Các Lỗi Phổ biến

  • Sử dụng khóa tự nhiên có thể thay đổi — nếu khóa chính thay đổi, mọi khóa ngoại tham chiếu đến nó cũng phải thay đổi. Sử dụng khóa thay thế để tránh điều này.
  • Thiếu index cho khóa ngoại — không có index trên cột khóa ngoại, các lệnh xóa và cập nhật trên bảng cha gây ra quét toàn bảng (Full Table Scan).
  • Lạm dụng CASCADEON DELETE CASCADE có thể âm thầm xóa lượng lớn dữ liệu. Sử dụng có chủ đích, không phải theo mặc định.
  • Không xử lý tham chiếu vòng (Circular References) — hai bảng tham chiếu lẫn nhau có thể làm cho insert và delete phức tạp. Phá vỡ chu trình bằng khóa ngoại nullable hoặc ràng buộc trì hoãn (Deferred Constraints).
  • Sử dụng GUID/UUID làm Clustered Index — UUID ngẫu nhiên gây phân mảnh chỉ mục (Index Fragmentation). Sử dụng UUID tuần tự hoặc số nguyên thay thế cho khóa chính Clustered.
  • Khóa chính tổng hợp (Composite Primary Key) trong bảng lớn — khóa chính nhiều cột làm tất cả các tham chiếu khóa ngoại rộng hơn và chậm hơn.Ưu tiên khóa thay thế một cột.

Best Practices

  1. Luôn định nghĩa khóa chính — mọi bảng nên có một. Không có nó, bạn không thể nhận diện hoặc cập nhật dòng một cách đáng tin cậy.
  2. Ưu tiên khóa thay thế (Surrogate Key) — sử dụng số nguyên tự tăng hoặc UUID. Giữ định danh tự nhiên dưới dạng ràng buộc UNIQUE.
  3. Luôn định nghĩa khóa ngoại — đừng dựa vào logic ứng dụng để duy trì mối quan hệ. Cơ sở dữ liệu thực thi tính toàn vẹn ngay cả khi ứng dụng có lỗi.
  4. Đánh chỉ mục mọi cột khóa ngoại — điều này quan trọng cho hiệu năng nối (Join Performance) và thực thi ràng buộc.
  5. Sử dụng RESTRICT theo mặc định — chỉ chọn CASCADE khi bạn có yêu cầu nghiệp vụ rõ ràng cho hành vi cascade.
  6. Giữ khóa chính nhỏ — INT hoặc BIGINT là lý tưởng. Mọi khóa ngoại nhân bản giá trị khóa chính, nên khóa nhỏ hơn nghĩa là chỉ mục nhỏ hơn và nối nhanh hơn.
  7. Không bao giờ thay đổi giá trị khóa chính — nếu bạn cần thay đổi định danh, thêm khóa thay thế mới và giữ giá trị cũ dưới dạng cột unique.

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

1. Sự khác biệt giữa Khóa chính (Primary Key) và Khóa ngoại (Foreign Key) là gì?

Khóa chính (Primary Key)Khóa ngoại (Foreign Key)
Mục đíchNhận diện duy nhất một dòngTham chiếu đến một dòng trong bảng khác
Tính duy nhấtPhải duy nhấtCó thể trùng lặp
NULLKhông thể NULLCó thể NULL
Mỗi bảngChính xác mộtKhông hoặc nhiều
Thực thiTính toàn vẹn thực thể (Entity Integrity)Tính toàn vẹn tham chiếu (Referential Integrity)
Tự đánh IndexCó (luôn luôn)Phụ thuộc vào cơ sở dữ liệu

2. Tính toàn vẹn tham chiếu (Referential Integrity) là gì và được thực thi như thế nào?

Tính toàn vẹn tham chiếu đảm bảo rằng mọi giá trị khóa ngoại khớp với một khóa chính tồn tại trong bảng được tham chiếu. Nó ngăn chặn:

  • Insert một dòng con với cha không tồn tại
  • Delete một dòng cha vẫn còn được tham chiếu bởi dòng con (trừ khi dùng CASCADE)
  • Update khóa chính của cha trong khi dòng con vẫn tham chiếu đến giá trị cũ

Nó được thực thi bởi cơ sở dữ liệu thông qua ràng buộc khóa ngoại (Foreign Key Constraint) — cơ sở dữ liệu kiểm tra mọi INSERT, UPDATE, và DELETE dựa trên ràng buộc.

3. Sự khác biệt giữa Khóa tự nhiên (Natural Key) và Khóa thay thế (Surrogate Key) là gì?

Khóa tự nhiên (Natural Key)Khóa thay thế (Surrogate Key)
NguồnDữ liệu nghiệp vụ (email, ISBN, SSN)Được tạo ra (tự tăng, UUID)
Có ý nghĩaCó — có ý nghĩa nghiệp vụKhông — định danh nhân tạo
Ổn địnhCó thể thay đổi theo thời gianKhông bao giờ thay đổi
Kích thướcCó thể lớn (chuỗi)Nhỏ và đồng nhất (INT, BIGINT)
Khuyến nghịSử dụng làm ràng buộc UNIQUESử dụng làm khóa chính

Khóa thay thế thường được ưu tiên vì ổn định, đơn giản và hiệu năng tốt.

4. Điều gì xảy ra khi bạn xóa một dòng được tham chiếu bởi khóa ngoại?

Nó phụ thuộc vào hành động ON DELETE được định nghĩa trên ràng buộc khóa ngoại:

Hành độngKết quả
RESTRICT / NO ACTIONLệnh xóa bị từ chối — lỗi được trả về
CASCADETất cả các dòng tham chiếu được tự động xóa
SET NULLCác cột khóa ngoại trong dòng tham chiếu được đặt thành NULL
SET DEFAULTCác cột khóa ngoại được đặt về giá trị mặc định

Mặc định là RESTRICT, là lựa chọn an toàn nhất.

5. Tại sao các cột khóa ngoại nên được đánh chỉ mục (Indexed)?

Nếu không có index trên cột khóa ngoại:

  • Xóa dòng cha yêu cầu quét toàn bảng (Full Table Scan) trên bảng con để xác nhận không có tham chiếu (hoặc tìm dòng để cascade)
  • Nối (Join) bảng cha và con chậm — cơ sở dữ liệu không thể tra cứu dòng khớp hiệu quả
  • Khóa (Locking) có thể leo thang từ mức dòng lên mức bảng trong một số cơ sở dữ liệu (ví dụ: MySQL InnoDB)

Đánh chỉ mục cột khóa ngoại đảm bảo tra cứu O(log n) thay vì quét O(n).

6. Một bảng có thể có nhiều Khóa ngoại không?

Có. Một bảng có thể có bao nhiêu khóa ngoại tùy cần, mỗi khóa tham chiếu đến một bảng khác (hoặc thậm chí cùng bảng cho mối quan hệ tự tham chiếu - Self-referencing).

CREATE TABLE order_items (
id INT PRIMARY KEY,
order_id INT REFERENCES orders(id), -- FK đến orders
product_id INT REFERENCES products(id), -- FK đến products
quantity INT
);

Ví dụ phổ biến:

  • order_items tham chiếu cả ordersproducts
  • employees tham chiếu departments (phòng ban) và employees (quản lý) — tự tham chiếu
  • posts tham chiếu users (tác giả) và categories (danh mục)

Tìm hiểu thêm