大家都是人,都會犯錯啦。尤其是在資料庫裡搞 foreign key 的細節時更容易出包。這堂課我會幫你避開最常見的錯誤跟陷阱。好的資料庫就像一座堅固的橋:只要哪裡出錯,整個結構就會垮掉。我們一起來搞懂怎麼讓「資料橋樑」穩穩的。
錯誤 1:foreign key 欄位沒加 index
你加了 foreign key,就是跟資料庫說:「把這兩張表連起來吧」。但如果你沒特別幫這個 foreign key 欄位加 index,當你查詢有關聯的表時,效能可能會掉到谷底。
問題範例:
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id)
);
看起來一切都很美好:表建好了,foreign key 也有。但如果你執行像這樣的查詢:
SELECT *
FROM orders
JOIN customers ON orders.customer_id = customers.customer_id;
如果資料量很大,這查詢會超慢,因為 PostgreSQL 找不到合適的 index 來優化 join。
怎麼避免:
一定要幫 foreign key 指到的欄位加 index。有時 PostgreSQL 會自動幫你加,但還是自己確認比較保險。
CREATE INDEX idx_customer_id ON orders(customer_id);
錯誤 2:建表順序錯誤
想像你在建表,但還沒建好要參照的表就先加 foreign key,PostgreSQL 會直接跟你翻臉,因為找不到目標表。
問題範例:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id)
);
-- 哎呀,customers 表還沒建好耶...
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
結果:PostgreSQL 直接報錯,因為 customers 表還不存在。
怎麼避免:
先建好要參照的表,再加 foreign key。順序很重要。正確做法如下:
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id)
);
錯誤 3:cascade 操作語法寫錯
foreign key 常常會加上 ON DELETE CASCADE 或 ON UPDATE RESTRICT 這種選項。但如果你不小心寫錯,資料庫行為就會很奇怪。比如刪掉一邊的資料,另一邊卻沒跟著動。
問題範例:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id) ON DELETE CASCADEE
);
細心一點就會發現拼錯了——CASCADEE 多了一個 E。PostgreSQL 不會讓你過關的。
怎麼避免:
拼對字就贏一半了。 不確定時,隨時查 PostgreSQL 官方文件。
錯誤 4:資料完整性被破壞
資料完整性是每個資料庫的聖地,foreign key 就是守護它的工具。但有時你忘了加 foreign key,結果一切都亂了套。
問題範例:
CREATE TABLE customers (
customer_id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT
);
-- 插入資料
INSERT INTO orders (customer_id) VALUES (999);
這裡我們幫一個不存在的客戶加了訂單。這樣資料就亂掉了,這筆訂單就「懸空」了。
怎麼避免:
一定要用 foreign key,這樣才不會讓一張表指到不存在的資料。正確寫法如下:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id)
);
現在如果你想插入「懸空」資料,系統會直接報錯。
錯誤 5:foreign key 錯誤被靜默忽略
有時候工程師會硬塞不合規的資料,用 INSERT ... ON CONFLICT。看起來很方便,但遇到 foreign key 時可能會出現奇怪的狀況。
問題範例:
INSERT INTO orders (order_id, customer_id)
VALUES (1, 999)
ON CONFLICT DO NOTHING;
結果:資料沒插進去,但資料庫也沒告訴你為什麼。你就失去掌控了。
怎麼避免:
如果用 ON CONFLICT,一定要先檢查資料。例如:
INSERT INTO orders (order_id, customer_id)
SELECT 1, 999
WHERE EXISTS (
SELECT 1 FROM customers WHERE customer_id = 999
);
錯誤 6:刪除被參照資料沒加 ON DELETE
如果你刪掉一筆被 foreign key 參照的資料,但沒加 ON DELETE CASCADE,那些相關的資料還會留在資料庫裡,關聯就亂了。
問題範例:
DELETE FROM customers WHERE customer_id = 1;
-- orders 裡 customer_id = 1 的資料還在。
怎麼避免:
加上 ON DELETE CASCADE,這樣相關資料會自動被刪掉:
CREATE TABLE orders (
order_id SERIAL PRIMARY KEY,
customer_id INT REFERENCES customers(customer_id) ON DELETE CASCADE
);
現在你刪掉客戶時,對應的訂單也會跟著消失。
錯誤 7:MANY-TO-MANY 關聯沒設好
在搞 MANY-TO-MANY 關聯時,有時會忘了加複合 primary key 或 index。
問題範例:
CREATE TABLE enrollments (
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id)
);
-- 哎呀!忘了加 PRIMARY KEY。
怎麼避免:
加上複合 primary key 或 unique index:
CREATE TABLE enrollments (
student_id INT REFERENCES students(student_id),
course_id INT REFERENCES courses(course_id),
PRIMARY KEY (student_id, course_id)
);
錯誤 8:循環參照
循環參照就是兩張表互相當 foreign key,這樣會變成死循環,插入資料時會出問題。
問題範例:
CREATE TABLE table_a (
id SERIAL PRIMARY KEY,
table_b_id INT REFERENCES table_b(id)
);
CREATE TABLE table_b (
id SERIAL PRIMARY KEY,
table_a_id INT REFERENCES table_a(id)
);
怎麼避免:
用 DEFERRABLE INITIALLY DEFERRED,讓 PostgreSQL 可以等到 transaction 結束再檢查資料完整性:
CREATE TABLE table_a (
id SERIAL PRIMARY KEY,
table_b_id INT REFERENCES table_b(id) DEFERRABLE INITIALLY DEFERRED
);
foreign key 出錯不只會拖慢開發,還可能讓資料出大問題。把這份清單當小抄,避開常見的「地雷」。記住:foreign key 是你的好朋友,不是敵人。只要用對方法,你的資料庫就會是長期專案的超穩基礎!
GO TO FULL VERSION