为了跟进我关于modeling relational data with nosql的问题,我已经阅读了几篇关于这个主题的文章:
Nosql doesn't mean non-relational
他们似乎建议nosql可以处理规范化的关系数据。
让我们继续我之前的例子,一个CMS系统,它有两种类型的数据:文章和作者,其中文章有作者的引用(通过ID)。
以下是系统需要支持的操作:
如果相同的数据存储在RDBMS上,我想了解这些操作的性能。 特别是,请指定操作是否使用MapReduce,需要多次访问nosql商店(链接),或pre-join
我想限制讨论基于文档的 nosql解决方案,如mongodb,couchdb和riak。
修改1:
Spring-data project可用于Riak和Mongodb
答案 0 :(得分:5)
只是想为可能有点好奇的人投入CouchDB答案。 :)
正如上面第一个答案中所提到的,将作者文档嵌入到文章文档中是不明智的,因此下面的示例假设有两种文档类型:文章和作者。
CouchDB使用通常用JavaScript编写的MapReduce查询(但可以使用Python,Ruby,Erlang等)。 MapReduce查询的结果在第一次请求时存储在索引中,并且存储的索引用于所有将来的查找。在进一步请求时,对数据库的更改将添加到索引中。
CouchDB的API完全基于HTTP,因此对数据库的所有请求都是各种URL的HTTP谓词(GET,POST,PUT,DELETE)。我将列出MapReduce查询(用JavaScript编写)以及用于从索引中请求相关结果的URL。
<强> 1。按ID和作者
获取文章执行此操作的最简单方法是两个直接文档查找:
GET /db/{article_id} GET /db/{author_id}
...其中{author_id}是从文章的author_id字段中获取的值。
<强> 2。获取特定作者的所有文章
MapReduce的
function (doc) {
if (doc.type === 'article') {
emit(doc.author_id, doc);
}
}
GET /db/_design/cms/_view/articles_by_author?key="{author_id}"
...其中{author_id}是作者的实际ID。
第3。查找按创建日期排序的作者的前10篇文章
MapReduce的
function (doc) {
function arrayDateFromTimeStamp(ts) {
var d = new Date(ts);
return [d.getFullYear(), d.getMonth(), d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()];
}
var newdoc = doc;
newdoc._id = doc.author_id;
newdoc.created_at = arrayDateFromTimeStamp(doc.created_at);
if (doc.type === 'article') {
emit(newdoc.created_at, newdoc);
}
}
可以在视图请求中使用?include_docs=true
在CouchDB中包含样式“连接”。如果在emit(第二个参数)的值侧包含“_id”键,则在查询参数中添加include_docs=true
将包含指定的“_id”引用的文档。在上面的例子中,我们'用引用的作者的“_id”(文章文档中的“author_id”的值)替换文档自己的“_id”(我们不再需要)。请求前10篇文章及其相关作者信息如下:
GET /db/_design/cms/_view/articles_by_date?descending=true&limit=10&include_docs=true
请求该URL将以类似于:
的格式返回最近10篇文章的列表{"rows":[ { "id":"article_id", "key":[2011, 9, 3, 12, 5, 41], "value":{"_id":"author_id", "title":"..."}, "doc":{"_id":"author_id", "name":"Author Name"} } ]}
使用相同的索引,您可以获得任何年份,月份,日期,小时等所有文档的列表,无论是否有作者数据。
还有一些方法可以使用视图排序规则将多个文档聚合在一个文档中(例如CMS中引用不同内容的页面)。我在七月份为CouchConf所做的这些幻灯片中有一些关于如何做到这一点的信息:http://www.slideshare.net/Couchbase/couchconfsfdesigningcouchbasedocuments
如果您有任何其他问题,请告诉我。
答案 1 :(得分:4)
通过id和作者
获取文章
<强> SQL 强>:
<强> MongoDB的强>:
获取特定作者的所有文章
<强> SQL 强>:
<强> MongoDB的强>:
查找按创作日期排序的作者的前10篇文章
<强> SQL 强>:
<强> MongoDB的强>:
articles.find().sort().limit(10)
,authors.find({$in:[article_authors]})
<强>摘要强>
在两种情况下,MongoDB需要一个额外的查询,但下面的大部分相同的总工作。在某些情况下,MongoDB通过网络返回的数据较少(没有重复的条目)。连接查询往往受限于要加入的所有数据都存在于同一个框中的要求。如果作者和文章生活在不同的地方,那么你最终会做两个查询。
MongoDB往往会获得更好的“原始”性能,因为它不会在每次写入时刷新到磁盘(因此它实际上是“持久性”权衡)。它还有一个小得多的查询解析器,因此每个查询的活动较少。
从基本的表现角度来看,这些事情非常相似。他们只是对您的数据做出不同的假设,以及您想要做出的权衡。
答案 2 :(得分:2)
对于MongoDB,您不会将嵌入文档用于作者记录。因此,预加入已经结束,它是多次访问数据库。但是,您可以缓存作者,只需要为每条记录进行一次第二次旅行。您指出的查询在MongoDB中非常简单。
var article = db.articles.find({id: article_id}).limit(1);
var author = db.authors.find({id: article.author_id});
如果您使用ORM / ODM管理应用程序中的实体,这将是透明的。这将是两次到数据库的旅行。它们应该是快速的反应,两个命中根本不应该是明显的。
查找给定作者的文章只是反过来......
var author = db.authors.find({id: author_name}).limit(1);
var articles = db.articles.find({author_id: author.id});
同样,两个查询但单个作者提取应该很快并且可以轻松缓存。
var articles = db.articles.find({}).sort({created_at: 1}).limit(10);
var author_ids = articles.map(function(a) { return a.author_id });
var authors = db.authors.find({id: { '$in': authors_ids }});
最后,再次,两个查询,但只是一点点复杂。您可以在mongo shell中运行它们以查看结果可能是什么样的。
我不确定写地图是否值得完成。几次快速往返可能会有更多的延迟,但mongo协议非常快。我不会过分担心它。
最后,这样做的实际性能影响......理想情况下,您只需要查询文档中的索引字段,它应该非常快。唯一的额外步骤是获取其他文档的第二次往返,具体取决于您的应用程序和数据库的结构,这可能不是什么大问题。您可以告诉mongo仅查看超过给定阈值的查询(默认情况下打开时默认为100或200毫秒),这样您就可以随时关注随着数据增长而占用的程序。
你在这里有一个RDMS不提供的装备更容易分解数据。当您将应用程序扩展到CMS之外以支持其他内容但使用相同的身份验证存储时会发生什么?它恰好是一个完全独立的DB,它在许多应用程序中共享。跨dbs执行这些查询要简单得多 - RDMS存储它是一个复杂的过程。
我希望这可以帮助您进行NoSQL发现!