44

在这个系统中,我们存储产品、产品图像(一个产品可以有多个图像)和一个产品的默认图像。数据库:

CREATE TABLE  `products` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `NAME` varchar(255) NOT NULL,
  `DESCRIPTION` text NOT NULL,
  `ENABLED` tinyint(1) NOT NULL DEFAULT '1',
  `DATEADDED` datetime NOT NULL,
  `DEFAULT_PICTURE_ID` int(10) unsigned DEFAULT NULL,
  PRIMARY KEY (`ID`),
  KEY `Index_2` (`DATEADDED`),
  KEY `FK_products_1` (`DEFAULT_PICTURE_ID`),
  CONSTRAINT `FK_products_1` FOREIGN KEY (`DEFAULT_PICTURE_ID`) REFERENCES `products_pictures` (`ID`) ON DELETE SET NULL ON UPDATE SET NULL
) ENGINE=InnoDB AUTO_INCREMENT=30 DEFAULT CHARSET=utf8;


CREATE TABLE  `products_pictures` (
  `ID` int(10) unsigned NOT NULL AUTO_INCREMENT,
  `IMG_PATH` varchar(255) NOT NULL,
  `PRODUCT_ID` int(10) unsigned NOT NULL,
  PRIMARY KEY (`ID`),
  KEY `FK_products_pictures_1` (`PRODUCT_ID`),
  CONSTRAINT `FK_products_pictures_1` FOREIGN KEY (`PRODUCT_ID`) REFERENCES `products` (`ID`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=20 DEFAULT CHARSET=utf8 ROW_FORMAT=DYNAMIC;

如您所见,products_pictures.PRODUCT_ID -> products.IDproducts.DEFAULT_PICTURE_ID -> products_pictures.ID,因此是循环参考。可以吗?

4

6 回答 6

60

不,这不好。表之间的循环引用很混乱。请参阅这篇(十年前的)文章:SQL By Design: The Circular Reference

一些 DBMS 可以处理这些,并且要特别小心,但是 MySQL 会有问题。


选项1

作为您的设计,使两个 FK 之一可以为空。这使您可以解决先有鸡还是先有蛋的问题(我应该首先插入哪个表?)。

虽然你的代码有问题。它将允许产品具有默认图片,该图片将引用另一个产品!

要禁止此类错误,您的 FK 约束应为:

CONSTRAINT FK_products_1 
  FOREIGN KEY (id, default_picture_id) 
  REFERENCES products_pictures (product_id, id)
  ON DELETE RESTRICT                            --- the SET NULL options would 
  ON UPDATE RESTRICT                            --- lead to other issues

这将需要表中的UNIQUE约束/索引才能定义上述 FK 并正常工作。products_pictures(product_id, id)


选项 2

另一种方法是从表中删除Default_Picture_ID列并在product表中添加一IsDefault BITpicture。这个解决方案的问题是如何只允许每个产品的一张图片打开那个位,而所有其他的都关闭它。在 SQL-Server(我认为是 Postgres)中,这可以通过部分索引来完成:

CREATE UNIQUE INDEX is_DefaultPicture 
  ON products_pictures (Product_ID)
  WHERE IsDefault = 1 ;

但是 MySQL 没有这样的特性。


选项 3

这种方法甚至允许您将两个 FK 列定义为NOT NULL使用可延迟约束。这适用于 PostgreSQL,我认为适用于 Oracle。检查这个问题和@Erwin 的答案:SQLAlchemy 中的复杂外键约束所有键列不为空部分)。

MySQL 中的约束不能延迟。


选项 4

方法(我觉得最干净)是删除Default_Picture_ID列并添加另一个表。FK 约束中没有圆形路径,所有 FK 列都将NOT NULL使用此解决方案:

product_default_picture
----------------------
product_id          NOT NULL
default_picture_id  NOT NULL
PRIMARY KEY (product_id)
FOREIGN KEY (product_id, default_picture_id)
  REFERENCES products_pictures (product_id, id)

与解决方案 1 一样,这也需要表中的UNIQUE约束/索引。products_pictures(product_id, id)


总而言之,使用 MySQL,您有两种选择:

  • 选项 1(可以为空的 FK 列)与上述更正以正确执行完整性

  • 选项 4(无可为空的 FK 列)

于 2012-05-05T01:45:20.687 回答
3

这只是建议,但如果可能,在此表之间创建一个连接表可能有助于跟踪

product_productcat_join
------------------------
ID(PK)
ProductID(FK)- product table primary key
PictureID(FK) - category table primary key
于 2012-05-04T10:01:25.323 回答
3

您将遇到的唯一问题是插入时。你先插入哪个?

有了这个,你将不得不做类似的事情:

  • 插入默认图片为空的产品
  • 使用新创建的产品 ID 插入图片
  • 更新产品以将默认图片设置为您刚刚插入的图片。

同样,删除不会很有趣。

于 2012-05-04T10:04:11.547 回答
1

在另一个表中,您可以只保存该字段而没有外键约束。在某些情况下,当您希望使用较小的表进行处理但使用处理结果连接到较大的表时,它很有用。

例如,如果您添加一个 product_location 表,其中包含国家、地区、城市、地址以及经度和纬度信息。可能存在您想在地图上的圆圈内显示产品的情况。

于 2019-03-29T03:28:28.567 回答
0

约翰,你所做的并没有什么不好,但使用 PK-FK 实际上有助于通过删除冗余重复数据来规范化你的数据。这有一些奇妙的优势

  • 由于消除了相同数据的重复存储位置,提高了数据完整性
  • 减少锁定争用并提高多用户并发性
  • 较小的文件
于 2012-05-05T02:00:54.153 回答
-2

那不是循环引用,即 pk-fk

于 2012-05-04T10:00:02.350 回答