Ràng buộc — NOT NULL, CHECK, UNIQUE, PRIMARY KEY, khóa ngoại — thường được xem thuần túy là công cụ bảo vệ dữ liệu: chặn giá trị sai lọt vào. Đúng, nhưng chưa đủ. Chúng còn là thông tin mà planner của PostgreSQL đọc để sinh kế hoạch tốt hơn. Một ràng buộc nói cho planner một sự thật đảm bảo về dữ liệu, và planner dùng sự thật đó để bỏ qua các bước thừa. Bài này đo thật ba ví dụ: một CHECK cho phép bỏ hẳn quét bảng, một PRIMARY KEY mở khóa phụ thuộc hàm, và một UNIQUE cho ước lượng chính xác.
Ràng buộc là thông tin, không chỉ là hàng rào
Điểm mấu chốt cần nhìn nhận: khi bạn khai CHECK (gia >= 0), bạn không chỉ chặn giá trị âm — bạn cam kết với planner rằng cột gia không bao giờ âm. Planner có thể tin cam kết đó tuyệt đối (khác với thống kê ANALYZE chỉ là ước lượng có thể sai). Từ cam kết đó, nó suy ra được nhiều điều để tối ưu.

Hình 1: Ràng buộc cho planner biết sự thật đảm bảo về dữ liệu. CHECK cho phép loại trừ truy vấn mâu thuẫn, PRIMARY KEY cho phụ thuộc hàm, UNIQUE cho ước lượng chính xác.
Đo thật: một CHECK bỏ hẳn quét bảng
Trên bảng sp 5 triệu dòng với cột gia có ràng buộc CHECK (gia >= 0), chạy một truy vấn mâu thuẫn với ràng buộc:

Hình 2: WHERE gia < 0 trên bảng có CHECK (gia >= 0) với constraint_exclusion=on: planner trả Result → One-Time Filter: false trong 0,025 ms (không quét). Không có CHECK (hoặc để mặc định): Parallel Seq Scan cả 5 triệu dòng, 40 ms.
- Có CHECK +
constraint_exclusion=on: kế hoạch làResult → One-Time Filter: false— 0,025 ms. Planner chứng minh rằng vìgialuôn>= 0, điều kiệngia < 0không thể khớp dòng nào, nên nó bỏ qua việc quét bảng hoàn toàn. - Không có CHECK (hoặc để mặc định):
Parallel Seq Scancả 5 triệu dòng — 40 ms.
Nhanh hơn ~1.600 lần — không phải nhờ index, mà nhờ planner suy luận từ ràng buộc. Lưu ý quan trọng: tính năng này gọi là constraint exclusion, và với bảng thường bạn phải bật SET constraint_exclusion = on (mặc định là partition, chỉ áp cho bảng phân vùng và kế thừa). Đây là công cụ mạnh cho các truy vấn sinh động có điều kiện đôi khi mâu thuẫn ràng buộc.
PRIMARY KEY: phụ thuộc hàm
Ràng buộc khóa chính cho planner (và trình phân tích cú pháp) biết một sự thật mạnh: mọi cột khác của bảng được xác định duy nhất bởi khóa chính. Điều này mở khóa "phụ thuộc hàm" trong GROUP BY:
-- id là PRIMARY KEY
SELECT id, ten, count(v) FROM t GROUP BY id; -- HỢP LỆ (chạy)
Bạn GROUP BY id nhưng lại SELECT ten (không gom nhóm) — thường điều này là lỗi SQL. Nhưng vì id là khóa chính, PostgreSQL biết mỗi id chỉ ứng với đúng một ten, nên ten không cần nằm trong GROUP BY. Nếu id không phải khóa chính, cùng truy vấn báo lỗi:
ERROR: column "t.ten" must appear in the GROUP BY clause or be used in an aggregate function
Đây vừa là tiện lợi cú pháp vừa là dấu hiệu planner hiểu cấu trúc dữ liệu — nó có thể tối ưu gom nhóm dựa trên tính duy nhất của khóa.
UNIQUE: ước lượng chính xác
Ràng buộc UNIQUE (và PRIMARY KEY) đảm bảo mỗi giá trị xuất hiện tối đa một lần. Nên với truy vấn bằng trên cột unique, planner ước lượng chính xác 1 dòng:
SELECT * FROM t WHERE ma = 500; -- ma UNIQUE → planner biết chắc rows=1
Ước lượng đúng dẫn tới lựa chọn kế hoạch đúng: khi JOIN, biết một phía trả về đúng 1 dòng khiến planner chọn Nested Loop với index thay vì Hash Join tốn kém. Điểm tinh tế: đảm bảo này đúng kể cả khi thống kê ANALYZE đã cũ — ước lượng từ thống kê có thể lệch sau khi dữ liệu đổi nhiều, nhưng ràng buộc UNIQUE là cam kết cứng planner luôn tin được.
Đánh đổi cần cân nhắc
Ràng buộc có chi phí ghi nhưng thường đáng. UNIQUE và PRIMARY KEY cần index (tốn ghi và dung lượng); CHECK/NOT NULL phải kiểm mỗi lần ghi. Nhưng chi phí này nhỏ, và đổi lại bạn được cả đúng đắn dữ liệu lẫn thông tin cho planner. Đừng bỏ ràng buộc để "tiết kiệm" — bạn mất cả hai lợi ích.
Khai ràng buộc đúng ngữ nghĩa, không phải để lừa planner. Ràng buộc phải phản ánh sự thật thật sự về dữ liệu. Khai NOT NULL cho cột thực ra có thể null, hay CHECK sai, sẽ khiến dữ liệu vi phạm bị từ chối (tốt) nhưng cũng có thể khiến planner suy luận sai nếu bạn lách bằng cách nạp dữ liệu vòng qua ràng buộc. Ràng buộc là hợp đồng — giữ đúng nó.
constraint_exclusion mặc định không bật cho bảng thường. Lợi ích loại trừ CHECK ở trên cần SET constraint_exclusion = on. Cân nhắc bật ở cấp truy vấn cho các báo cáo sinh động có điều kiện đôi khi mâu thuẫn ràng buộc; bật toàn cục tốn chút thời gian lập kế hoạch cho mọi truy vấn nên cân theo tải thật.
Ba ý mang về
- Ràng buộc là thông tin cho planner, không chỉ hàng rào dữ liệu: một
CHECK (gia >= 0)cho phép planner chứng minhWHERE gia < 0không khớp dòng nào và bỏ hẳn quét bảng — đo thật 0,025 ms so với 40 ms (~1.600×), vớiconstraint_exclusion=on. PRIMARY KEYmở khóa phụ thuộc hàm: planner biết mọi cột được xác định bởi khóa chính, nênSELECT ten ... GROUP BY idhợp lệ khiidlà PK (không PK thì báo lỗi phải đưatenvàoGROUP BY).UNIQUEcho ước lượng chính xác đảm bảo: truy vấn bằng trên cột unique ước lượng đúng 1 dòng, dẫn tới lựa chọn join đúng — và cam kết này đúng kể cả khi thống kêANALYZEđã cũ, điều ước lượng thông thường không có.
Phần sau ta đi vào cơ chế nền tảng giải thích nhiều hành vi lưu trữ và VACUUM: Phần sau mổ xẻ MVCC — vì sao mỗi UPDATE không sửa tại chỗ mà tạo một phiên bản dòng mới, để lại dòng cũ chờ dọn, và điều đó ảnh hưởng hiệu năng ra sao.