5

为了跟进我关于使用 nosql 建模关系数据的问题,我阅读了几篇关于该主题的文章:

Nosql 不代表非关系型

Nosql电子商务示例

他们似乎暗示 nosql 可以处理规​​范化的关系数据。

因此,让我们继续我之前的示例,一个 CMS 系统,它有两种类型的数据:文章和作者,其中文章有作者的引用(按 ID)。

以下是系统需要支持的操作:

  1. 通过 id 和作者一起获取文章
  2. 获取特定作者的所有文章
  3. 查找按创建日期排序的作者的前 10 篇文章

如果相同的数据存储在 RDBMS 上,我想了解这些操作与相同操作的性能。 特别是,请指定该操作是否使用 MapReduce、需要多次访问 nosql 存储(链接)或预加入

我想只讨论基于文档的nosql 解决方案,如 mongodb、couchdb 和 riak。

编辑1:

Spring-data 项目在 Riak 和 Mongodb 上可用

4

3 回答 3

5

只是想为任何可能好奇的人提供一个 CouchDB 答案。:)

正如上面第一个答案中提到的,将作者文档嵌入到文章文档中是不明智的,因此下面的示例假设了两种文档类型:文章和作者。

CouchDB 使用通常用 JavaScript 编写的 MapReduce 查询(但 Python、Ruby、Erlang 等可用)。MapReduce 查询的结果在第一次请求时存储在索引中,并且存储的索引用于所有未来的查找。根据进一步的请求,对数据库的更改会添加到索引中。

CouchDB 的 API 完全基于 HTTP,因此对数据库的所有请求都是各种 URL 上的 HTTP 动词(GET、POST、PUT、DELETE)。我将列出 MapReduce 查询(用 JavaScript 编写)以及用于从索引请求相关结果的 URL。

1.通过id和作者一起获取文章

执行此操作的最简单方法是两个直接文档查找:

获取 /db/{article_id}
获取 /db/{author_id}

...其中 {author_id} 是从文章的 author_id 字段中获得的值。

2.获取特定作者的所有文章

MapReduce

function (doc) {
  if (doc.type === 'article') {
    emit(doc.author_id, doc);
  }
}
获取 /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 中包含样式“连接” 。如果您在发出的值侧(第二个参数)包含一个“_id”键,那么添加include_docs=true到您的查询参数将包括指定的“_id”引用的文档在上述情况下,我们将替换文档自己的“_id”(我们不再需要)与被引用作者的“_id”(文章文档中“author_id”的值)。请求前 10 篇文章及其相关作者信息如下所示:

获取 /db/_design/cms/_view/articles_by_date?descending=true&limit=10&include_docs=true

请求该 URL 将返回最近 10 篇文章的列表,格式类似于:

{“行”:[
  { "id":"article_id",
    “关键”:[2011, 9, 3, 12, 5, 41],
    "值":{"_id":"author_id", "title":"..."},
    "doc":{"_id":"author_id", "name":"作者姓名"}
  }
]}

使用相同的索引,您可以获得任何年、月、日、小时等粒度的所有文档列表,无论是否包含作者数据。

还有一些方法可以使用视图归类将多个文档聚合到一个文档中(例如 CMS 中引用不同内容的页面)。在我 7 月份为 CouchConf 做的这些幻灯片中有一些关于如何做到这一点的信息:http ://www.slideshare.net/Couchbase/couchconfsfdesigningcouchbasedocuments

如果您有任何其他问题,请告诉我。

于 2011-10-03T16:14:06.613 回答
4

通过 id 和作者一起获取文章

SQL

  • 1 个查询
  • 2 索引查找
  • 2 数据查询
  • 返回的数据 = 文章 + 作者

MongoDB

  • 2 个查询
  • 2 索引查找
  • 2 数据查询
  • 返回的数据 = 文章 + 作者

获取特定作者的所有文章

SQL

  • 1 个查询
  • 1 索引查找
  • N 次数据查找
  • 返回的数据 = N 篇文章

MongoDB

  • 1 个查询
  • 1 索引查找
  • N 次数据查找
  • 返回的数据 = N 篇文章

查找按创建日期排序的作者的前 10 篇文章

SQL

  • 1 个查询
  • 2 索引查找
  • 11 到 20 个数据查找(文章然后是唯一作者)
  • 返回的数据 = 10 篇文章 + 10 位作者

MongoDB

  • 2 个查询 ( articles.find().sort().limit(10),authors.find({$in:[article_authors]})
  • 2 索引查找
  • 11 到 20 个数据查找(文章然后是唯一作者)
  • 返回的数据 = 10 篇文章 + 1 到 10 位作者

概括

在两种情况下,MongoDB 需要一个额外的查询,但在下面完成大部分相同的总工作。在某些情况下,MongoDB 通过网络返回的数据较少(没有重复的条目)。连接查询往往受到所有要连接的数据都存在于同一个盒子上的要求的限制。如果 Authors 和 Articles 位于不同的地方,那么无论如何您最终都会进行两次查询。

MongoDB 倾向于获得更好的“原始”性能,因为它不会在每次写入时都刷新到磁盘(因此它实际上是一种“持久性”权衡)。它还有一个更小的查询解析器,因此每个查询的活动更少。

从基本性能的角度来看,这些东西非常相似。他们只是对您的数据和您想要做出的权衡做出不同的假设。

于 2011-10-01T01:29:36.827 回答
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 中运行这些程序以查看结果可能是什么样的。

我不确定这是否值得写一个 map reduce 来完成。几个快速的往返可能会有更多的延迟,但 mongo 协议非常快。我不会过分担心它。

最后,这样做的实际性能影响......因为理想情况下您只查询文档中的索引字段,它应该非常快。唯一的额外步骤是获取其他文档的第二次往返,这取决于您的应用程序和数据库的结构,这可能根本不是什么大问题。您可以告诉 mongo 仅对超过给定阈值(打开时默认为 100 或 200 毫秒)的查询进行分析,这样您就可以关注随着数据的增长您的程序所花费的时间。

RDMS 不提供的一种适合您的方法是更容易分解数据。当您将应用程序扩展到 CMS 之外以支持其他事物但使用相同的身份验证存储时会发生什么?它现在恰好是一个完全独立的数据库,在许多应用程序之间共享。跨数据库执行这些查询要简单得多——使用 RDMS 存储是一个复杂的过程。

我希望这对您的 NoSQL 发现有所帮助!

于 2011-10-01T01:14:39.047 回答