【问题标题】:When should foreign keys be used? [closed]什么时候应该使用外键? [关闭]
【发布时间】:2013-09-21 08:57:11
【问题描述】:

我刚刚开始学习 SQL(使用 SQLite),我正在尝试弄清楚何时应该使用外键。向我解释的方式是,只要出现重复数据,就应该使用外键,只需保存 ID 以节省空间。我正在创建的数据库中有几千条记录,列出了类别和县(每列中可能有几十个唯一值)。所以我可以用县名和主键 id 为县制作一个单独的表,并对类别做同样的事情。我毫不怀疑它会使数据库缩小约 5%。但这是唯一的好处吗?似乎它使其他一切变得更加复杂。为原本不需要的县和类别添加 ID。在 phpLiteAdmin 中查看表格时,它只显示一个数字而不是类别/县名,使其更难以可视化。在这种情况下使用外键并制作单独的表有什么好处?还是我不应该这样做,而是将所有数据(重复和所有数据)都放在一张表中?另外 - 让县/类别表只有一列没有数字主键是否有意义,因为无论如何它们都是唯一的?这至少会在 phpLiteAdmin 中显示全名。提前致谢!

【问题讨论】:

  • 您真正要问的是“什么是‘规范化数据库?’以及“使用标准化数据有什么好处(和权衡)?”
  • 我很困惑为什么这个问题被标记为离题。给出的原因是我要代码。但是我没有要求任何代码。

标签: sql sqlite


【解决方案1】:

如果您使用外键。它也称为参照完整性。

假设您有两个表,第一个表是 account_user,第二个表是 account_user_detail。 因此 account_user 表将具有 account_id 的 account_number 的主键。 account_user_detail 表将包含帐户持有人地址详细信息。 因此,如果您将两个表关联起来,那么 account_number 或 account_id 将是相同的。 所以使用第二个表中的主键值我们定义外键。 外键标识第二个表中 account_number 的值是第一个表中具有相同帐号的 Xyz 先生的引用。

因此,外键用于将两个表与两个表共有的列连接起来并共享相同的唯一值。

【讨论】:

  • @Nimal Kumar:参照完整性是指许多 RDBM 在数据库更新期间保证外键的一致性。说“外键也称为参照完整性”是不正确的,因为在使用它们时没有必要强制执行参照完整性。例如,3.6 之前的 SQLite 版本不提供引用完整性作为数据库功能(必须启用引用完整性才能与当前版本一起使用)。
  • 我不是 100% 确定我遵循...听起来你只是在解释什么是外键。我理解外键很好并且经常使用它们。我的问题是,如果父表只有一列数据,何时使用它们才有意义。如果将这一列唯一数据用作主键是否有意义。
【解决方案2】:

您可以查看this:

SQL 外键约束用于强制“存在”关系 表格之间。

编辑:-

外键约束的存在是为了保证被引用的行存在。

wiki 也说:-

数据库设计的一个重要部分是确保 现实世界实体之间的关系反映在 数据库引用,使用外键从一个表引用到 另一个。[9]数据库设计的另一个重要部分是数据库 规范化,其中表被分开并且外键使 他们有可能被重建。

也检查这个线程。

Why are foreign keys more used in theory than in practice?

【讨论】:

  • 这是他们唯一的优势吗?在插入新行时确保县/类别存在?
  • @DanGoodspeed:- 当您想从表中删除某些行时,这也可能对您有所帮助。此外,在表中使用外键还有很多优点。 注意:- 在 SQLITE 中,外键默认是禁用的
  • @DanGoodspeed:- 我还建议您理解规范化的概念,因为这绝对会帮助您! :)
  • 每次打开与数据库的连接时,我都会打开外键,但感谢您的提醒。我相信我非常了解规范化的概念,我只是在询问拥有单列父表的好处。到目前为止,我还没有听说过它限制了可以写入该列的内容。
  • @DanGoodspeed:- 存在外键约束是为了保证引用的行存在。
【解决方案3】:

如果您的国家名称是“美国”,则为 24 字节。如果使用外键,则只需要 2-4 个字节。那是一个巨大的差异。

当您搜索国家/地区名称时,它会非常快,因为您只需匹配一个数字而不是整个字符串。

此外,如果您在 country_id 字段上使用索引,它会变得更小。

我能理解你关于增加复杂性的观点。在您的情况下,您可以不使用外键而侥幸,但您不应该这样做。您最终将需要它们,因此在该主题上更好地做好准备和经验。

【讨论】:

  • 我想我的想法是这样的 - 所有的类别和县只是一个词,通常大约 10 个字符左右。平均一行数据大约是 250 个字符。所以从 250 字节到 242 字节真的没有那么大的区别。我也不是 100% 在速度上出售的。在您的情况下,它必须查找美国的国家/地区 ID,然后转到主表并查找具有该 ID 的行。如果在搜索之前不知道国家/地区 ID,这对计算机来说似乎是一个额外的步骤。
  • 是的,这是一个额外的步骤。但是匹配 1000 个字符串比匹配 1000 个数字要慢
【解决方案4】:

但这是唯一的好处吗?

没有。

外键在逻辑上类似于大多数编程语言中的指针或引用。想象一下,试图通过复制数据来创建一些数据结构,而不能引用任何东西。没有外键的数据库也会有类似的问题。

如果无法引用事物,您必须确保所有副本都保持最新。如果存在导致一个副本被更新但另一个副本没有更新的错误,那将有效地损坏数据 - 您将不再知道哪个副本是正确的。

避免冗余主要不是关于空间,而是关于数据完整性。数据库规范化(没有外键就无法完成)的全部目的是避免冗余,从而保护数据完整性。


在你的特殊情况下......

  • 一个类别(或国家)是否应该能够存在而不连接到主表中的任何行?
  • 某个类别是否应该存在任何数据,独立于该类别连接到主表中的哪些行?
  • 是否有任何操作(如重命名)需要独立完成?

如果任一答案为“是”,则应将类别放入单独的查找表中。此查找表是否应该使用自然(名称)或代理(ID)键是一个不同的问题。列出了一些优缺点here

【讨论】:

  • 我理解外键很好,在引用我试图获取的单列表时,这是它们的必要性。你的三个问题的答案是不,不,也许。我的客户可能想要重命名类别的建议确实存在。并且在类别表中只更改一次名称会更容易,然后遍历主表并每次更改它。虽然这将是一个非常罕见的情况。更罕见的是一个县决定改名。
  • @DanGoodspeed 您写道:“我刚刚开始学习 SQL(使用 SQLite),我正在尝试弄清楚何时应该使用外键。”。如果您不想要最终从我们这里得到的一般性介绍性答案,您应该采用不同的措辞。对于您的具体问题:根据您的回答,您可能不需要单独的查找表(以及随附的 FK)。这完全取决于您需要代表什么样的需求,而且您比我们更了解您的需求......
【解决方案5】:

外键约束用于限制允许存在的值在一列或一组列中。以婚姻为例:

CREATE TABLE person
        (person_id INTEGER NOT NULL PRIMARY KEY
        , name varchar NOT NULL
        );

CREATE TABLE marriage
        ( person1 INTEGER NOT NULL PRIMARY KEY
        , person2 INTEGER NOT NULL UNIQUE
        , comment varchar
        , CONSTRAINT marriage_1 FOREIGN KEY (person1) REFERENCES person(person_id)
        , CONSTRAINT marriage_2 FOREIGN KEY (person2) REFERENCES person(person_id)
        , CONSTRAINT order_in_court CHECK (person1 < person2)
        );

-- add some data ...
INSERT INTO person(person_id,name) values (1,'Bob'),(2,'Alice'),(3,'Charles');

INSERT INTO marriage(person1,person2, comment) VALUES(1,2, 'Crypto marriage!') ; -- Ok
INSERT INTO marriage(person1,person2, comment) VALUES(2,1, 'Not twice!' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(3,3, 'No you dont...' ) ; -- Should fail
INSERT INTO marriage(person1,person2, comment) VALUES(2,3, 'OMG she did it again.' ) ; -- Should fail (does not)
INSERT INTO marriage(person1,person2, comment) VALUES(3,4, 'Non existant persons are not allowed to marry !' ) ; -- Should fail

SELECT p1.name, p2.name, m.comment
FROM marriage m
JOIN person p1 ON m.person1 = p1.person_id
JOIN person p2 ON m.person2 = p2.person_id
        ;

上述 DDL 尝试对婚姻进行建模(但部分失败)要建模的约束是:

  • 只有现有的人可以结婚
  • 婚姻只能存在于两个不同的人之间
  • 一个人只能结婚一次

输出:

INSERT 0 3
INSERT 0 1
ERROR:  new row for relation "marriage" violates check constraint "order_in_court"
ERROR:  new row for relation "marriage" violates check constraint "order_in_court"
INSERT 0 1
ERROR:  insert or update on table "marriage" violates foreign key constraint "marriage_2"
DETAIL:  Key (person2)=(4) is not present in table "person".
 name  |  name   |        comment        
-------+---------+-----------------------
 Bob   | Alice   | Crypto marriage!
 Alice | Charles | OMG she did it again.
(2 rows)

【讨论】:

  • 我理解外键很好,当引用我试图获取的唯一单列表(如“类别”)时,这是它们的必要性。到目前为止,我被告知它会节省一点空间,这使得在列中插入非类别变得更加困难,并且更容易重命名。都是很小的原因。
  • 这与空间无关,与速度无关。这是关于正确性。 FK 约束是数据模型的一部分。并且数据模型仅代表外部世界的(受限)视图。 (例如:你只能嫁给一个已经存在的人,这是完全有道理的:在现实世界中你也不能嫁给一个不存在的人)
猜你喜欢
  • 2017-09-11
  • 1970-01-01
  • 2012-12-23
  • 2021-07-13
  • 1970-01-01
  • 1970-01-01
  • 2022-01-02
  • 2011-07-19
  • 1970-01-01
相关资源
最近更新 更多