CodeGym /課程 /SQL SELF /使用 foreign key 時常見的錯誤

使用 foreign key 時常見的錯誤

SQL SELF
等級 20 , 課堂 4
開放

大家都是人,都會犯錯啦。尤其是在資料庫裡搞 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 CASCADEON 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 是你的好朋友,不是敵人。只要用對方法,你的資料庫就會是長期專案的超穩基礎!

2
任務
SQL SELF, 等級 20, 課堂 4
上鎖
連鎖更新
連鎖更新
2
任務
SQL SELF, 等級 20, 課堂 4
上鎖
使用 ON DELETE CASCADE
使用 ON DELETE CASCADE
1
問卷/小測驗
資料完整性檢查,等級 20,課堂 4
未開放
資料完整性檢查
資料完整性檢查
留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION