我试图找出 NoSQL KVP 或文档数据库中的外键和索引的等价物。由于没有关键表(添加标记两个对象之间关系的键),我真的很难理解如何以对普通网页有用的方式检索数据。
假设我有一个用户,这个用户在整个网站上留下了很多评论。我能想到的跟踪用户评论的唯一方法是
将它们嵌入到用户对象中(这似乎没什么用) 创建并维护一个 user_id:comments 值,其中包含每个评论的键 [comment:34, comment:197, etc...] 的列表,以便我可以将它们作为需要。
但是,以第二个示例为例,当您使用它来跟踪其他内容时,您很快就会碰壁积极评论。它也很容易出现竞争条件,因为许多页面可能会同时尝试更新它。
如何在 NoSQL 数据库中跟踪如下关系?
用户的所有评论
所有活跃评论
所有带有[关键字]标签的帖子
俱乐部中的所有学生 - 或学生所在的所有俱乐部
还是我想错了?
关于如何以“NoSQL 方式”存储多对多关联的所有答案都归结为同一件事:冗余存储数据。
在 NoSQL 中,您不会根据数据实体之间的关系来设计数据库。您根据将针对它运行的查询来设计数据库。使用与非规范化关系数据库相同的标准:如果数据具有凝聚力更重要(考虑以逗号分隔的列表而不是规范化表中的值),那么就这样做。
但这不可避免地会以牺牲其他类型的查询(给定用户对任何文章的评论)为代价来优化一种类型的查询(例如,任何用户对给定文章的评论)。如果您的应用程序需要对两种类型的查询进行同等优化,则不应进行非规范化。同样,如果您需要以关系方式使用数据,则不应使用 NoSQL 解决方案。
非规范化和冗余存在冗余数据集彼此不同步的风险。这称为异常。当您使用规范化的关系数据库时,RDBMS 可以防止异常。在非规范化数据库或 NoSQL 中,编写应用程序代码以防止异常成为您的责任。
有人可能认为 NoSQL 数据库为您完成防止异常的艰苦工作会很棒。有一种范式可以做到这一点——关系范式。
couchDB 方法建议在 map 阶段发出适当的东西类,并在 reduce 中对其进行总结。因此,您可以映射所有评论并为给定用户发出 1
,然后仅打印出来。然而,它需要大量的磁盘存储来构建 couchDB 中所有可跟踪数据的持久视图。顺便说一句,他们也有这个关于关系的 wiki 页面:http://wiki.apache.org/couchdb/EntityRelationship。
另一方面,Riak 拥有建立关系的工具。它是链接。您可以将链接(此处为注释)文档的地址输入到“根”文档(此处为用户文档)。它有一个技巧。如果它是分发的,它可能会一次在多个位置进行修改。它会导致冲突,结果是巨大的矢量时钟树:/ ..不是那么糟糕,不是那么好。
Riak 还有另一个“机制”。它有 2 层键名空间,即桶和键。因此,以学生为例,如果我们有俱乐部 A、B 和 C 以及学生 StudentX、StudentY,您可以保持以下约定:
{ Key = {ClubA, StudentX}, Value = true },
{ Key = {ClubB, StudentX}, Value = true },
{ Key = {ClubA, StudentY}, Value = true }
并读取关系只需列出给定存储桶中的键。那有什么问题?这该死的慢。列出存储桶从来都不是 riak 的优先事项。它越来越好。顺便提一句。您不会浪费内存,因为此示例 {true}
可以链接到 StudentX 或 Y 的单个完整配置文件(此处不可能发生冲突)。
如您所见,NoSQL!= NoSQL。您需要查看具体的实现并自己进行测试。
之前提到的列存储看起来很适合关系..但这完全取决于您的 A 和 C 以及 P 的需求;)如果您不需要 A 并且您的字节数少于 Peta 就离开它,继续使用 MySql 或 Postgres。
祝你好运
user:userid:comments 是一种合理的方法 - 将其视为 SQL 中的列索引的等价物,并附加要求您不能对未索引的列进行查询。这是您需要考虑您的要求的地方。包含 3000 万个项目的列表并非不合理,因为它很慢,而是因为用它做任何事情都不切实际。如果您的真正要求是显示一些最近的评论,那么您最好保留一个非常短的列表,以便在添加评论时更新 - 请记住,NoSQL 没有规范化要求。竞争条件是基本键值存储中列表的问题,但通常要么您的平台正确支持列表,您可以使用锁做一些事情,或者您实际上并不关心失败的更新。与用户评论相同 - 创建索引关键字:posts 更多相同 - 可能是作为学生属性的俱乐部列表和该字段上的索引以获取俱乐部的所有成员
你有
"user": {
"userid": "unique value",
"category": "student",
"metainfo": "yada yada yada",
"clubs": ["archery", "kendo"]
}
"comments": {
"commentid": "unique value",
"pageid": "unique value",
"post-time": "ISO Date",
"userid": "OP id -> THIS IS IMPORTANT"
}
"page": {
"pageid": "unique value",
"post-time": "ISO Date",
"op-id": "user id",
"tag": ["abc", "zxcv", "qwer"]
}
那么在关系数据库中,正常的做法是在一对多关系中对数据进行规范化。这与您在 NoSQL 数据库中所做的相同。只需索引您将用来获取信息的字段。
例如,对您来说重要的索引是
评论.用户ID
评论.PageID
评论.PostTime
页面标签[]
如果您使用 NosDB (A .NET based NoSQL Database with SQL support),您的查询将类似于
SELECT * FROM Comments WHERE userid = ‘That user’;
SELECT * FROM Comments WHERE pageid = ‘That user’;
SELECT * FROM Comments WHERE post-time > DateTime('2016, 1, 1');
SELECT * FROM Page WHERE tag = 'kendo'
从他们的 SQL cheat sheet 或文档中检查所有支持的查询类型。
尽管在这种情况下最好使用 RDBMS 而不是 NoSQL,但一种可能的解决方案是维护额外的节点或集合来管理映射和索引。它可能会以额外的集合/节点和处理的形式产生额外的成本,但它将提供易于维护和避免数据冗余的解决方案。