什么时候可以在数据库中接受循环引用?
理论和实践,任何帮助表示赞赏。
考虑城市和州。每个城市都存在于一个州内。每个州都有一个首都。
CREATE TABLE city (
city VARCHAR(32),
state VARCHAR(32) NOT NULL,
PRIMARY KEY (city),
FOREIGN KEY (state) REFERENCES state (state)
);
CREATE TABLE state (
state VARCHAR(32),
capital_city VARCHAR(32),
PRIMARY KEY (state),
FOREIGN KEY (capital_city) REFERENCES city (city)
);
第一个问题 - 您不能如图所示创建这些表,因为外键不能引用表中不(尚)存在的列。解决方案是在没有外键的情况下创建它们,然后再添加外键。
第二个问题 - 您不能在任一表中插入行,因为每个插入都需要另一个表中预先存在的行。解决方案是将其中一个外键列设置为 NULL,然后分两个阶段插入该数据。例如
--Create state record
INSERT INTO state (state, capital_city) VALUES ('Florida', NULL);
--Create various city records
INSERT INTO city (city, state) VALUES ('Miami', 'Florida');
INSERT INTO city (city, state) VALUES ('Tallahassee', 'Florida');
INSERT INTO city (city, state) VALUES ('Orlando', 'Florida');
--Set one of the cities as the capital
UPDATE state SET capital_city = 'Tallahassee' WHERE state = 'Florida';
指向其他记录的记录在数据库中很有用。有时这些记录形成一个循环。这可能仍然有用。实践中唯一真正的烦恼是避免违反约束。
例如,如果您有一个用户和事务表,用户可能有一个指向他的最后一个事务的指针。您需要先插入事务,然后将其更新last_transaction_id
为正确的值。虽然这两个记录都存在,但您无法删除它们,因为user.last_transaction_id
指向transaction.id
和transaction.user_id
指向user.id
。这意味着没有交易的用户有一个 null last_transaction_id
。这也意味着您必须先将该字段设为空,然后才能删除交易。
管理这些外键约束是一件痛苦的事,但它肯定是可能的。如果您稍后将约束添加到数据库,从而引入新的循环依赖项,则可能会出现问题。在这种情况下你必须小心。但是,只要循环中的一条记录具有可为空的外键字段,就可以打破循环并删除记录。只要您以正确的顺序插入记录,更新通常不会成为问题。
这在技术上是可行的,但在删除记录时会导致各种问题,因为它会产生鸡与蛋的问题。这些问题通常会采取激烈的措施,例如手动删除 FK 并删除有问题的项目来解决。
如果你有这样的关系:
create table foo_master (
foo_master_id int not null primary key
,current_foo_id int
)
create table foo_detail (
foo_detail_id int not null primary key
foo_master_id int not null
)
alter table foo_master
add constraint fk_foo_current_detail
foreign key (current_foo_id)
references foo_detail
alter table foo_detail
add constraint fk_foo_master
foreign key (foo_master_id)
references foo_master
然后由于循环依赖关系,删除记录可能会导致这样的鸡和聚集问题。
一个更好的模式看起来像:
create table foo_master (
foo_master_id int not null primary key
)
create table foo_detail (
foo_detail_id int not null primary key
foo_master_id int not null
is_current char (1)
)
alter table foo_detail
add constraint fk_foo_master
foreign key (foo_master_id)
references foo_master
这意味着该关系是非循环的,并且仍然可以识别“当前” foo_detail 记录。
Oracle 分层查询语法的最新添加之一 -NOCYCLE
关键字 - 明确用于此目的 - 处理数据中的循环引用。我看不出有什么问题,之前也不得不处理这种模型。这并不太难,尤其是在支持可延迟约束的 Oracle 中。
我已经看到出于性能原因进行了循环引用。虽然它看起来很丑,性能可能可以忽略不计。
示例:一些公告板(我认为 phpBB 会这样做)在类别表中有一个 lastpostid,它是线程中最后一个帖子的快捷方式。
这将创建一个圆圈,其中最后一个帖子有一个指向类别表的 FK,而类别表有一个指向最后一个帖子的 FK。
就像我说的,我不是很喜欢它,但我已经看到它完成了。
应该像瘟疫一样避免循环引用。可以建立双向关系,甚至与自己的关系(如果你是一张桌子),但循环依赖只是自找麻烦。
我很少遇到必要的 1:1 关系并强加循环关系
请注意,这种关系中的外键字段必须可以为空,否则您永远无法从表中删除行
如果您使用的是只写数据库,我想这不是问题。如果您计划使用 CRUD 的 RUD 部分,您可能会在处理它们时遇到(通常可以避免的)复杂问题。