优点和缺点。让我们从您的第一种方法是 1NF 而您的第二种方法不是我所理解的事实开始(可能是,见下文),以及含义是什么。基本上,您想从第一种方法开始,但请注意,在某些环境中,您必须执行第二种方法。
首先我们所说的原子是什么意思(之所以这么说是因为大多数人都搞错了):如果一个值引用了其域中的单个值,那么它就是原子的。这并不排除数组,但必须将数组视为适合原子值的(SQL 数组基本上是一个数学矩阵,它们是序数)。正如我在其他地方指出的那样,如果序数很重要,数组就是原子的。 top_five int[] 是原子的,ip_address int[] 也是原子的(将 IP 地址表示为 int 值的有序数组)。这意味着您可以对组执行您对单个元素无法执行的操作。一般来说,有两件事会破坏原子性:集合(如果我没记错的话,我认为你在这里尝试这样做,但是集合是无序的,而 sql 数组是有序的,所以如果你依赖于排序,这不适用)和案例其中一个数组成员在功能上依赖于另一个数组成员(此处不适用)。
所以我们在这里讨论的是 PostgreSQL 中第一范式与非第一范式设计的优缺点。此外,您在这里有一个明确的案例,因此可以不抽象地谈论这些,而是具体地谈论这些。
与大多数人不同的是,我对这两种方法都有亲身体验。我的建议是尽可能使用第一种方法,但如果需要,请理解第二种方法。
写入并发
您的第一种方法将支持比您的第二种方法更好的写入并发性(请参阅下面有关索引的更多信息)。如果您想在添加另一个相似性的同时删除一个相似性(都在同一本书中),则在第一种情况下事务不会相互锁定,但在第二种情况下它们会,因为两者都存储在同一行中并且需要等待行锁。
换句话说,甚至在我们开始讨论索引更新性能(这将是真正的开销)之前,第一个就可以更好地扩展写入。
数据完整性
您遇到的第二个问题是数据完整性。可以在第一种情况下定义独特的约束,而在第二种情况下不能很好地表达。你可以表达它们,但它们需要更多的维护工作。如果你走这条路,你需要更仔细地思考,你可能需要编写自定义函数来检查你需要验证的内容。这是更多的工作,它还增加了写入性能(远高于维护唯一索引的能力。
您还必须编写约束触发器来解决如果您删除一本书会发生什么。这可能是痛苦的和另一个维护问题。在我参与的项目使用这种方法的情况下,我们通常会接受某些数据会不一致,并构建能够容忍缺失链接的东西。这是一个非常重要的权衡,但有时是必要的。
索引和读取性能
PostgreSQL 有 GIN 索引,可以轻松索引数组成员。某些情况下的 GIN 索引是您选择第二种情况的唯一原因,但它们也并非没有成本。 GIN 索引更新成本高,但读取效率高。所以人们经常在 PostgreSQL 中打破自动性来做一些事情,比如存储标签数组。
如果您的数据很少更新,您有重大的读取性能问题,并且已经用尽了其他可能性,那么这种方法是有意义的,尤其是如果您可以容忍此处的孤立链接。
总体推荐
对于一般情况,您的第一个解决方案要好得多。在某些情况下,第二个效果更好,但是您需要注意,当您开始这样做时会弹出很多问题....