假设我要存放我的应用程序的用户,与Facebook类似,本身之间的关系。
这意味着,如果A是B的朋友(或某种关系),则B也是A的朋友。 为了保存这个关系目前我打算将它们存储在表中的关系如下
UID FriendID
------ --------
user1 user2
user1 user3
user2 user1
但是在这里,我面临两个选择:
- 典型情况下,在那里我会同时存储
user1 -> user2
和user2->user1
。 这将需要更多的空间,但(至少在我的头)需要的只是一个传过来的行显示特定用户的朋友。 - 另一个选择是将任
user1->user2
OR user2->user1
,每当我想找到的所有的朋友user1
,我将查询表上的两列,将找到用户的朋友。 这将需要一半的空间,但(再次,至少在我的头上)两倍的时间量。
首先,是我的推理合适? 如果是的话,是没有办法,我差点忘(在缩放/吞吐量或任何方面的)任何瓶颈?
基本上,两者之间有什么取舍,比这里列出的其他。 此外,在行业一个优于其他?
下面是这两种方法都会在数据库中物理上表示:
让我们来分析这两种方法...
方法1(存储在表的两个方向):
- PRO:简单的查询。
- CON:数据可以通过插入/更新被破坏/删除仅在一个方向。
- MINOR PRO:不需要额外的限制,以确保友谊不能重复。
- 进一步的分析需要:
- 领带:一个指数涵盖了两个方向,所以你并不需要一个辅助指标。
- TIE:存储需求。
- TIE:性能。
接近2(存储在表中只有一个方向):
- CON:更为复杂的查询。
- PRO:不能腐败被忘记办理相反的方向,因为没有相反方向的数据。
- MINOR CON:需要
CHECK(UID < FriendID)
所以相同的友谊永远不能以两种不同的方式来表示,而且按键上(UID, FriendID)
可以做自己的工作。 - 进一步的分析需要:
- TIE:两个索引是必要的覆盖查询(在综合指数的两个方向
{UID, FriendID}
和综合指数{FriendID, UID}
- TIE:存储需求。
- TIE:性能。
点1是特别感兴趣的。 MySQL的/的InnoDB 总是 簇的数据,二级指标可以在群集表(见“集群的缺点”昂贵的这篇文章 ),所以它可能看起来好像在方法上2次指数将吃了较少的行的所有优点。 然而 ,二次索引包含准确相同的字段作为主(仅在以相反的顺序),所以有在该特定情况下,没有存储开销。 也没有指针表堆(因为没有桌子堆),因此它可能是更便宜的存储明智的,正常的基于堆的指数。 并假设该查询覆盖有索引,不会有一个双查找通常与一个簇表或者是二级索引相关联。 所以,这基本上是一个连接(均未方法1也不方法2具有显著优势)。
点2是有关第1点:不要紧是否我们将有N个值的B树或两个B-树中,每个与N / 2的值。 所以这也是一个领带:这两种方法都会使用,占用存储的大致相同量。
同样的道理也适用于3点 :我们是否寻找一个更大的B-Tree或2级较小的,并没有太大的差别,所以这也是一个平局。
所以,对于稳健性,尽管有些丑陋查询,并需要额外的CHECK
,我会用这种方法去2。
存储价格相对便宜,这些天,所以我不会担心,因为这一点。
什么会关心我的是,你现在必须清理你的两倍存储信息。 所以,如果你“取消关注”的人,你必须删除2条,不只是一个。
其他考虑因素是搜索和索引。 有可能是散列的用户ID的组合来检查脑干的优势,只要你遵循洽约定(像往常一样散列之前更高的ID追加到更低)。
所以,现在你有其他的可能性。 您是否有兴趣在查询2个用户之间的关系? 或者是更重要的是看一个用户的属性?
这是什么系统将做什么顾虑。 看看像DDD(领域驱动设计)和CQRS(命令查询责任隔离)科目来看看如何瓜分你的应用程序,所以每个区域中可能最简单的方法来实现。 这会给你的渠道进行微调,后来不优化运行到复杂的问题。
尽管在方案1和方案2之间选择茨尔Dimitrijevic的选择,你应该考虑这个问题:
就是你想设计对称的或不对称的关系?
例如(坏的榜样,但仍说明了我的观点),如果你只是想知道,这两个用户是否为家人或朋友,那么该链接是对称的。 如果一个用户是其他的家庭成员,则反是真实的。 方法2可能会被考虑。
但是如果你想喜欢什么类型的家庭更具体的信息,一个人到另一个(是他们的父亲,儿子,叔父?),那么它变得不对称。 如果A是B的父亲,则B是A的儿子/女儿。 方法1可能成为必要。