我有一个问题,为什么大多数人选择一种有缺陷的方式在关系数据库中建模“一对多”关系。
为了简单起见,让我们考虑一个应用程序:
这几乎是许多处理文档和工作流程的“事务”应用程序的基本用例。
对于数据库建模,(至少)有两种主要方法可以完成任务。 我将在这个例子中使用 MariaDB 来说明。
方法 1 – 仅通过外键链接的独立数据库实体 在这种方法中:
CREATE TABLE `order` (
id_order INT,
orderfield1 VARCHAR(255),
PRIMARY KEY (id_order)
);
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id_order | int(11) | NO | PRI | NULL | |
| orderfield1 | varchar(255) | YES | | NULL | |
+-------------+--------------+------+-----+---------+-------+
和
CREATE TABLE `orderitem` (
id_order INT,
id_item INT,
orderitemfield1 VARCHAR(255),
PRIMARY KEY (id_item),
FOREIGN KEY (`id_order`) REFERENCES `order`(`id_order`)
);
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| id_order | int(11) | YES | MUL | NULL | |
| id_item | int(11) | NO | PRI | NULL | |
| orderitemfield1 | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+-------+
方法 2 – “拥有”实体中的复合键 在这种方法中:
CREATE TABLE `order` (
id_order INT,
orderfield1 VARCHAR(255),
PRIMARY KEY (id_order)
);
+-------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+-------+
| id_order | int(11) | NO | PRI | NULL | |
| orderfield1 | varchar(255) | YES | | NULL | |
+-------------+--------------+------+-----+---------+-------+
和
CREATE TABLE `orderitem` (
id_order INT,
id_item INT,
orderitemfield1 VARCHAR(255),
PRIMARY KEY (id_order, id_item),
FOREIGN KEY (`id_order`) REFERENCES `order`(`id_order`)
);
+-----------------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-----------------+--------------+------+-----+---------+-------+
| id_order | int(11) | NO | PRI | NULL | |
| id_item | int(11) | NO | PRI | NULL | |
| orderitemfield1 | varchar(255) | YES | | NULL | |
+-----------------+--------------+------+-----+---------+-------+
我的观点是:实际的业务用例是决定我们应该采用方法 1 还是方法 2 的唯一参数。 在我看来,“一对多”(实体 A – 实体 B)关系可以反映两个业务需求:
我认为,在建模“没有实体 A 就无法存在实体 B”的一对多关系时,方法 2 是大多数业务用例中的最佳方法。 为什么 ?想象一下 orderitem 表包含 10 亿条记录,并且用户正在显示订单文档。 在方法1中,为了提高性能,将在非主id_order列上创建索引。所以除了10亿长的表之外,还会有另外10亿长的db实体用于b树索引。这将使数据库存储基础设施变得混乱。 在方法 1 中,应用程序服务器中的错误也可能导致 orderitem 表中的记录违反业务功能规则。
方式二中,复合主键的id_order已经作为索引,可以在log(n)时间内获取相关的实体B记录。 基础设施上无需添加额外的存储。
但是,在对“实体 B 可以在没有实体 A 的情况下存在”一对多关系进行建模时,方法 1 可能是最佳方法,但前提是大多数查询仅在实体 B 上运行。
所以我的问题是:您认为我的分析正确吗?如果是,为什么大多数数据库开发人员选择外键方法而不是复合键方法,而不考虑业务现实......?
大多数 ORM 强烈鼓励方法 1(使用外键),无论功能需求是什么/无论性能如何
好的,这里有很多东西要解压。
所以除了10亿长的表之外,还会有另外10亿长的数据库实体用于b树索引
是的,在 MySQL 中,需要为此目的创建和维护一个“辅助”索引。这在 InnoDB 和(SQL Server)中也是有效的,因为它们使用聚集索引模型。老实说,现在十亿条目的索引不算小,但也不算大。我在 DB2 中有一个 9 亿行的表,有 4 个索引(每个索引 2-6 列)。没什么大不了的。
应用程序服务器中的错误可能会导致 orderitem 表中的记录违反业务功能规则不,这两种解决方案在这方面都是安全的。应用程序(任何应用程序)将无法插入不符合外键约束的数据。不用担心这个。这方面的错误将被视为关键错误,并且会快速修复。
你觉得我的分析正确吗...
是的,您的分析是正确的,尽管偏向于性能优化。我完全同意你的观点,因为我的重点也是性能优化。然而,你需要现实一点,考虑到许多开发人员并不真正理解它的基本原理(我花了几十年的时间)。再说一遍,这没有什么问题,因为在大多数情况下,引擎可以对其进行补偿(例如 Oracle 自动索引、DB2 自动调整等)
大多数 ORM 强烈鼓励方法 1 ...
非成熟我发现大多数
ORM 确实鼓励这种方法,特别是基于 JavaScript 或 TypeScript 的方法。这些是新的 ORM,其实现并不全面,并且在使用复合键时实际上存在错误。我认为这个建议不是理论性的,而是实用性的,因为它们经常因复合键而崩溃。我确信这些错误会及时得到修复。 我希望我能够为您有趣的问题添加更多细节。