我了解什么是外键,并且在对我设计的所有数据库表有意义的地方都包含它们。
然而,一直让我困惑的是我是否应该明确设置“更新时”和“删除时”功能(缺乏更好的术语)。例子:
CREATE TABLE "test1"
(
id serial,
referenceid integer,
FOREIGN KEY (referenceid) REFERENCES "othertable" (id) ON UPDATE CASCADE ON DELETE CASCADE
)
Run Code Online (Sandbox Code Playgroud)
此代码特意显式添加了技术上“不必要”的部分:“ON UPDATE CASCADE ON DELETE CASCADE”。
既然这不是默认执行的,那么一定有一个原因!毕竟,默认行为始终(或至少应该始终是)最常用的行为:
CREATE TABLE "test2"
(
id serial,
referenceid integer,
FOREIGN KEY (referenceid) REFERENCES "othertable" (id)
)
Run Code Online (Sandbox Code Playgroud)
在 test1 表中,据我了解,如果“othertable”更改“id”列值或删除任何记录,则意味着 test1 表中引用的记录将被更新或删除。从表面上看,这似乎应该是默认行为。
在 test2 表中,再次据我了解,如果“othertable”更改“id”列值,或删除任何记录,这意味着如果 test2 中有记录,PostgreSQL 将拒绝执行查询参考正在修改的内容。
我基本上对“更新时”和“删除时”的整个概念感到困惑。为什么有人希望这样的查询被拒绝呢?而且“CASCADE”甚至不是唯一的选择(除了没有);您可以使用多个其他值来导致各种行为(我不明白)。
由于表之间存在规定的关系(通过外键),因此您希望它们保持一致难道不是重点吗?那么,如果“主”表发生更改,为什么您不希望它“级联”呢?
这可能类似于我永远无法理解为什么面向对象编程在代码中具有“安全措施”,使您无法直接更改或检索对象的属性并被迫通过“getters”和“setters”。我的意思是,如果某些东西可以在您的数据库中执行查询,那么不是“全部丢失”了吗?他们可以这样做:
DELETE FROM table1/table2 CASCADE
Run Code Online (Sandbox Code Playgroud)
... 或类似的东西。
ON UPDATE/ON DELETE 机制似乎几乎就像数据库工程师无法决定最佳行为,而是将其交给产品的用户。对我来说,这增加了很多困惑和焦虑。
应该注意的是,我过去多次使用“test2”样式代码,只是意识到我无法在有意义的地方更新或删除记录。这就是为什么我在询问和了解之后首先开始使用“ON UPDATE CASCADE ON DELETE CASCADE”。
那么为什么这不是数据库的默认行为,甚至不是唯一的行为呢?为什么您希望更新/删除“主记录”的查询失败?
我不确定ON UPDATE CASCADE。如果您发现自己需要这种级联更新,那么这可能是您的数据库设计中的“代码味道”。理论上,您的主键应该是静态的,因此不需要发生需要级联的更改。也许它是作为 的一个逻辑步骤添加的ON DELETE CASCADE。至少比级联删除安全。
的存在ON DELETE CASCADE更有意义:虽然 PK 不应该真正改变,但事情确实经常被删除。级联只是为了方便。它使您不必在删除父实体时编写代码来手动删除子实体。
此外,它可能被认为比在其他逻辑中实现它更安全,因为数据库正在处理事务一致性、死锁等,因此应该保证操作(允许错误)是原子的。如果您实现自己的“查找子级,删除,然后删除父级”,这可能必须嵌套,您必须做一些跑腿工作[!]以确保如果中间出现错误,则无法删除一行的曾曾曾孙,但让其余的保持不变(留下部分删除的实体,这可能会导致以后难以诊断问题)。
\n[!] 采用适当的锁,最好不要锁定整个表,确保事务隔离设置正确并不像乍看起来那么简单。
\n为什么不采用级联?
\n正如我上面所说,我认为定期级联更新的需要有点设计味道。您不需要在正常操作期间更改主键值。
\n尽管更手动删除复杂的结构化实体存在错误的危险,但我对级联删除非常警惕。您经常会看到缺乏经验的人使用-then-re-方法执行UPSERT操作[*],即使数据库支持单语句 upsert 操作[^],如果启用级联删除,这会损坏您的数据:删除也会删除子项,并且它们不会被后续插入放回原处。DELETEINSERT
此外,在很多情况下,对于真实数据,您实际上并不需要级联删除。例如,如果经理离开公司,您不想删除他们的下属,因为首先分配新经理被忘记或被错误阻止。
\n[*] 在 postgres via 中,INSERT ... ON CONFLICT ...但这不是标准且完全不同的语法[\xe2\x80\xa0]在其他地方使用
\n [^] 要么是因为他们不知道可用的语法,要么是为了避免使用它跨数据库兼容
\n [\xe2\x80\xa0]MERGE可以在微软的 TSQL 中使用,达到同样的效果,mySQL [\xe2\x80\xa1]支持INSERT ... ON DUPLICATE KEY ...
\n [\xe2\x80\xa1] mySQL 也支持REPLACE INTO,但 IIRC 只是删除+插入的语法糖,因此具有相同的危险
理想情况下,您的 PK永远不会改变,因此没有理由CASCADE修改子表的 PK。这是许多人使用合成密钥(例如 UUID 或SERIAL值)的原因之一。这些不是“真正的”钥匙,但它们非常方便。(真实键是表中所有行中应该唯一的属性或属性组合。)对于行中的真实键,您可以(并且通常应该)添加约束UNIQUE。Postgres 擅长这类事情。
就目前而言DELETE,这取决于表格。如果子记录只应与父记录一起存在(它是“依赖实体”),则删除子记录是正确的。另一方面,如果子级可以在没有父级的情况下存在(它是一个“独立实体”),那么这NULL是更好的解决方案。
请注意,如果您想在DELETE或时保留历史数据TRUNCATE,您可以在 Postgres 中轻松完成此操作。其他人可以多说,我在需要此类功能的地方使用语句级触发器。