Cách khắc phục nhanh
PostgreSQL đang chặn Khóa ngoại (Foreign Key) của bạn vì cột mà bạn đang trỏ tới không được đảm bảo là duy nhất. Để một mối quan hệ hoạt động, cột của bảng cha (như order_id) phải có ràng buộc PRIMARY KEY hoặc UNIQUE.
Chạy lệnh này để thêm ràng buộc còn thiếu:
ALTER TABLE orders ADD CONSTRAINT orders_order_id_unique UNIQUE (order_id);
Sau khi áp dụng, việc tạo FOREIGN KEY của bạn sẽ thành công ngay lập tức.
Tại sao lỗi này xảy ra
Giả sử ứng dụng của bạn quản lý 5.000 giao dịch mỗi ngày. Bạn có bảng orders và bảng order_items. Nếu bạn cố gắng liên kết chúng bằng một cột không duy nhất, Postgres sẽ không thể xác định được dữ liệu.
-- Thiết lập bảng cha này gây ra lỗi
CREATE TABLE orders (
id SERIAL,
tracking_code TEXT -- Không có ràng buộc unique ở đây!
);
-- Việc tạo bảng con này sẽ thất bại
CREATE TABLE order_items (
id SERIAL,
sku_name TEXT,
order_ref TEXT REFERENCES orders(tracking_code)
);
Database engine sẽ dừng bạn lại với lỗi matching given keys. Điều này là do Khóa ngoại đóng vai trò như một con trỏ. Nếu tracking_code cho phép hai hàng có cùng một giá trị, Postgres sẽ không biết mặt hàng đó thuộc về đơn hàng cụ thể nào.
Nguyên lý hoạt động: Các quy tắc ràng buộc
Tính toàn vẹn dữ liệu là điều bắt buộc trong PostgreSQL. Ngay cả khi bảng của bạn hiện có 100% giá trị duy nhất, cơ sở dữ liệu vẫn yêu cầu một đảm bảo ở cấp độ schema. Nó không chỉ tin vào dữ liệu hiện tại; nó cần một ràng buộc chính thức.
Các nguyên nhân phổ biến gây ra trở ngại này bao gồm:
- **Cột thông thường:** Bạn đang tham chiếu đến một cột tiêu chuẩn thiếu chỉ mục Unique.
- **Không khớp Khóa tổ hợp (Composite Key):** Bạn đang liên kết hai cột (ví dụ: `tenant_id` và `user_id`), nhưng bảng cha chỉ định nghĩa chúng là duy nhất một cách riêng lẻ.
- **Xung đột kiểu dữ liệu:** Cố gắng liên kết `BIGINT` với `INT` đôi khi có thể gây ra các lỗi khó hiểu hoặc ảnh hưởng đến hiệu suất.
Ba cách để khắc phục mối quan hệ
1. Thêm ràng buộc Unique
Sử dụng cách này nếu cột phải giữ nguyên kiểu TEXT hoặc VARCHAR nhưng cần là một mục tiêu tham chiếu hợp lệ. Điều này phổ biến khi sử dụng các ID dễ đọc như INV-2024-001.
ALTER TABLE orders ADD CONSTRAINT unique_tracking_code UNIQUE (tracking_code);
2. Tham chiếu Khóa chính (Cách tốt nhất)
Trong hầu hết các môi trường thực tế, việc tham chiếu đến cột id sẽ an toàn và nhanh hơn. Các phép join dựa trên số nguyên (Integer) hoạt động tốt hơn đáng kể so với các phép join dựa trên chuỗi khi bạn đạt tới hơn 100.000 hàng.
-- Thay đổi bảng con của bạn để trỏ đến ID thay thế
ALTER TABLE order_items
ADD CONSTRAINT fk_order_id
FOREIGN KEY (order_id)
REFERENCES orders(id);
3. Giải quyết vấn đề Khóa tổ hợp
Khi mối quan hệ của bạn phụ thuộc vào nhiều cột, Postgres yêu cầu một ràng buộc đa cột duy nhất. Bạn không thể chỉ trỏ vào hai cột unique riêng biệt.
Cách khắc phục:
-- Tạo một ràng buộc bao gồm cả hai cột
ALTER TABLE orders ADD CONSTRAINT store_order_composite_unique UNIQUE (store_id, order_id);
Cách kiểm tra Schema của bạn
Trước khi thử lại migration, hãy kiểm tra trạng thái hiện tại của bảng bằng giao diện psql. Nhập \d orders để xem định nghĩa bảng.
Indexes:
"orders_pkey" PRIMARY KEY, btree (id)
"unique_tracking_code" UNIQUE CONSTRAINT, btree (tracking_code)
Nếu bạn không thấy nhãn UNIQUE CONSTRAINT bên cạnh cột mục tiêu, Khóa ngoại sẽ tiếp tục bị lỗi. Khi chỉ mục đã hiển thị, bạn có thể tiếp tục.
Danh sách kiểm tra cuối cùng
- Cột cha có ràng buộc `UNIQUE` hoặc `PRIMARY KEY` không?
- Các kiểu dữ liệu có khớp chính xác không (ví dụ: `UUID` với `UUID`)?
- Nếu sử dụng khóa tổ hợp, thứ tự các cột trong FK của bạn có khớp với thứ tự trong ràng buộc Unique không?

