CodeGym /课程 /SQL SELF /使用外键时常见的错误

使用外键时常见的错误

SQL SELF
第 20 级 , 课程 4
可用

使用外键时常见的错误

大家都是人,谁都会犯错。尤其是在数据库里用外键这些细节上。这节课我会帮你避开最常见的坑和暗礁。一个好的数据库就像一座结实的桥:哪儿出错了,整座桥都可能塌。咱们来看看怎么让你的“数据桥”稳稳当当的。

错误1:外键没有索引

你加外键的时候,其实就是在告诉数据库:“把这两张表关联起来”。但如果你没给这个外键专门建个索引,等你查大表、做复杂查询时,性能可能会掉得很惨。

问题示例:

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)
);

看起来一切都挺好:表建好了,外键也有了。但如果你写个这样的查询:

SELECT * 
FROM orders 
JOIN customers ON orders.customer_id = customers.customer_id;

数据量一大,这种查询就会很慢,因为PostgreSQL找不到合适的索引来优化join。

怎么避免:

一定要给外键指向的字段建索引。有时候PostgreSQL会自动帮你建,但最好自己手动加一遍,保险。

CREATE INDEX idx_customer_id ON orders(customer_id);

错误2:建表顺序不对

想象一下,你在建表,但还没建被引用的表就想加外键。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表还没建。

怎么避免:

先建被引用的表,再建带外键的表。顺序很重要。正确姿势如下:

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:级联操作语法写错

外键经常会加上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:破坏数据完整性

数据完整性是数据库的命根子,外键就是用来保证它的。但有时候你忘了加外键,数据就乱套了。

问题示例:

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);

这里我们给了一个根本不存在的客户下单。这样数据就不完整了,这个订单就成了“孤儿”。

怎么避免:

一定要用外键,防止表之间出现“鬼数据”。正确写法:

CREATE TABLE orders (
    order_id SERIAL PRIMARY KEY,
    customer_id INT REFERENCES customers(customer_id)
);

现在再插“孤儿”数据就会直接报错。

错误5:外键错误被悄悄吞掉

有时候开发者会用INSERT ... ON CONFLICT强行插入不合法的数据。看起来挺方便,但外键一旦出错,后果很难发现。

问题示例:

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

如果你删了主表的数据,但没加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关系时,很多人忘了加复合主键或者索引。

问题示例:

CREATE TABLE enrollments (
    student_id INT REFERENCES students(student_id),
    course_id INT REFERENCES courses(course_id)
);

-- 哎呀!忘了PRIMARY KEY。

怎么避免:

加个复合主键或者唯一索引:

CREATE TABLE enrollments (
    student_id INT REFERENCES students(student_id),
    course_id INT REFERENCES courses(course_id),
    PRIMARY KEY (student_id, course_id)
);

错误8:循环引用

循环引用就是两张表互相做外键,结果插数据时死循环,麻烦大了。

问题示例:

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在事务结束后再检查完整性:

CREATE TABLE table_a (
    id SERIAL PRIMARY KEY,
    table_b_id INT REFERENCES table_b(id) DEFERRABLE INITIALLY DEFERRED
);

外键用不好,不光开发慢,还可能让数据出大问题。把这份清单当小抄,别踩这些常见的坑。记住:外键是你的好帮手,不是敌人。用对了,你的数据库就是长期项目的坚实地基!

1
调查/小测验
数据完整性检查第 20 级,课程 4
不可用
数据完整性检查
数据完整性检查
评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION