您所说的结构类型在关系数据库中最有意义(我假设您所说的“传统”是指这种结构)。因为您有用户之间的订阅,所以这是一种关系,因此,关系数据库最有意义。关系数据库允许不同表之间的显式连接。
文档数据库(即保存 JSON 文档的数据库)对于这种类型的数据来说是一个非常糟糕的主意。文档数据库可以非常擅长某些事情,但是高度相互依赖的数据(例如某种订阅系统)是文档数据库的不良用途。我会在我们继续进行时解释更多。
您说您的图边是无向的,但您称它们为“订阅”这一事实告诉我它们实际上是有向的:一个用户订阅另一个用户。如果它是无方向的,它更像是在 Facebook 上加好友或在 LinkedIn 上连接:如果我和你是朋友,那么你必须和我成为朋友。在订阅系统(比如 Google+ 或 Twitter)中,即使我订阅了你,你也不一定需要订阅我。如果我们都订阅了对方,那么它实际上是两条有向边:一条是我给你的,一条是你给我的。
因此,最好的解决方案是至少有两个表:一个主“users”表和一个辅助“subscriptions”表。 “users”表将包含 uid、name、email 等列。 “subscriptions”表只有两列:subscriber 和 subscription。两者都包含来自“users”表的uid 值,并且每对值在表中必须是唯一的。
你问过这么多订阅是否会“膨胀”。首先,您假设您将成为下一个 facebook,并且需要与数百万或数十亿用户打交道。不用担心,至少一开始不会有这个问题。其次,大多数关系数据库都是logarithmic in their performance 用于检索和插入记录,随着用户数量的增加,它可以很好地扩展。对于您期望从文档数据库或磁盘上的 JSON 文件获得的行为类型,您的行为将是linear time complexity,因为您需要遍历数据库中的每个文档确保您已检查所有订阅(线性行为比对数更糟糕),否则您将需要在所有记录中复制订阅者/订阅信息。第二种解决方案确实会变得臃肿,因为您正在复制大量数据,更重要的是,它冒着很容易失去同步的巨大风险。在这种情况下,不同步比您想象的要容易得多。
为了向您展示如何执行此操作,我将使用sqlite3 dialect of SQL。这是我最常做的原型,所以我最熟悉它。将其转换为 MySQL 或 PostgreSQL 之类的东西应该相当简单。以下是创建数据库的语句:
# since `uid` is the primary key, just pass it a
# null value on insertion and the database will
# generate a unique integer and use that automatically.
# it might also be good to make more than just the uid unique,
# such as their email.
CREATE TABLE users (uid INTEGER PRIMARY KEY,
name TEXT,
email TEXT);
# we will use the uid for the foreign key reference since this should
# never change, even if the user changes their name or email.
CREATE TABLE subrs (subscriber INTEGER,
subscription INTEGER,
# make sure each entry of pairs is unique
CONSTRAINT uc_edges UNIQUE (subscriber,subscription),
# be sure subscribers can only be created for users that exist
CONSTRAINT fk_subr FOREIGN KEY (subscriber) REFERENCES users(uid),
# be sure subscription can only be created for users that exist
CONSTRAINT fk_subee FOREIGN KEY (subscription) REFERENCES users(uid)
);
这通常有一个很好的附加好处,即您无法删除订阅了他们的用户,除非您首先删除这些订阅。根据您选择的数据库 YMMV,请查看您选择的数据库的文档。几乎所有 SQL 数据库还支持使用外键的行为,即您无法使用尚不存在的外键值创建记录。使用 JSON 文件或文档数据库,很容易留下悬空订阅或让用户删除需要很长时间,因为您需要修改引用给定用户的每个用户文档。关系 SQL 数据库可以简化很多原本可以在代码中完成的事情。在您的应用程序代码中处理此逻辑会为您的数据处理带来更多错误和错误的机会。一点建议:您可以卸载到数据库的任何工作,您应该卸载到您的数据库。专业数据库比您的代码经过更好的测试,并且已经为您可能想要对数据执行的许多常见类型的操作提供了逻辑。
要查找用户的订阅,您可以执行如下查询:
SELECT * FROM subrs WHERE subscriber=some_uid;
要获取给定用户的所有订阅者,查询同样简单:
SELECT * FROM subrs WHERE subscription=some_uid;
删除用户记录只需大约三行:
DELETE FROM subrs WHERE subscription=some_uid;
DELETE FROM subrs WHERE subscriber=some_uid;
DELETE FROM users WHERE uid=some_uid;
在文档数据库中,您将有更多的应用程序代码来执行非常相似的操作,并且您的应用程序代码可能存在逻辑不良和破坏数据的风险。
TL;DR
使用关系 SQL 数据库。您可以在记录之间创建显式关系。因此,它不会像使用文档数据库那样容易让自己陷入困境(因为所有关系都只是隐含的)。像 MySQL 这样的 SQL 数据库也倾向于更好地扩展,无论是纵向(即具有更多用户记录)还是横向(即具有更多副本服务器)。