数据点

文档数据库到底是什么?

Julie Lerman

 

Julie Lerman
到现在为止,您很有可能至少已听说过 NoSQL 一词。MSDN 杂志 撰写了多篇关于它的文章。我非常尊重的许多人都对它的出现感到激动万分,我是在关系数据库领域成长起来的,希望对这方面有更好的了解。为了解相关信息,我做了大量研究,还请求朋友提供帮助,在此我将分享我所了解的关于 NoSQL 数据库的一小部分(称为“文档数据库”)的内容。另一小部分是键值对数据库。例如,我在 2010 年 7 月份“数据点”专栏中撰写的“Windows Azure 表存储”(msdn.microsoft.com/magazine/ff796231) 就是一个键值对 NoSQL 存储。

我应该先解释 NoSQL 的定义。它已成为非常普遍,或许有点使用过度的术语。该术语涵盖非关系性的数据存储机制,因此不需要使用 SQL 来访问其数据。在 CouchDB 专家兼作者 Bradley Holt 的博客文章“解释 NoSQL 评论”(bit.ly/rkphh0) 中,他说他已听到人们“将 NoSQL 重新定义为‘不仅仅是 SQL’”。他的观点是,这无论如何都不是一个反 SQL 活动。我认同这种观点,因为我一直信奉要为合适的任务选择合适的工具。

多数归入非关系领域的数据库都参与实现速度和可伸缩性的共同目标。通过放弃关系存储模型和架构,这些数据库便不受紧密结合的架构对其施加的限制,您的应用程序也不再需要联接各个表中的数据。

因为有许多可用文档数据库,所以我将重点介绍其中两个最常见的文档数据库 - MongoDB (mongodb.org) 和 CouchDB (couchdb.apache.org),以及 RavenDB (ravendb.net),后者是为 Microsoft .NET Framework 编写的,并且越来越受到用户的欢迎(请参阅本期专栏中的文章“将 RavenDB 嵌入 ASP.NET MVC 3 应用程序中”)。尽管您可以通过访问数据库网站来了解有关各个数据库以及使其彼此互不相同的更多详细信息,但这仍然处于较高的水平。

除某些转换(我将在本文中指出)外,这些数据库通常通过 HTTP 提供其数据,将其数据存储为 JavaScript Object Notation (JSON) 文档,并以多种语言提供 API。要注意的总体问题是简洁性、速度和可伸缩性。同样重要的是,所有这三项都是开源项目。

在我的研究中,我曾听到 MongoDB 专家说产品的主要问题是性能。一位 CouchDB 专家的目标是简洁性和可靠性(“我们希望成为数据库领域的本田雅阁”)。而 Ayende Rahien(RavenDB 的创建者)说,RavenDB 旨在“快速编写、快速读取和世界和平”。其中每个文档数据库提供的内容比这些话语片段所暗示的内容更多。

关系数据库的备用产品(而非替代产品)

NoSQL 和文档数据库提供了关系数据库的备用产品(而非替代产品)。每种产品都有各自的用处,它们只是为您提供了更多选择。但如何选择呢?一个重要的标准是一致性、可用性和分区容忍性 (CAP) 原则。它要求在使用分布式系统时,您只能选择三项保证(C、A 或 P)中的两项,因此您必须选择重要的一项。如果一致性最重要,则您需要优先考虑关系数据库。

例如,通常情况下,在银行应用领域或运行核设施的应用领域,一致性是最重要的保证。在这些情况下,必须随时考虑每个数据块。如果某人取了款,那么您的确需要在查看他的帐户余额时了解这一点。因此,您可能需要一个关系数据库来对其交易进行高级别控制。您经常会听到的一个术语是“最终一致性”,在 RavenDB 网站上表示为:“过时也比脱机好”。在其他领域,最终一致性已足够。即使您要检索的数据没有精确到毫秒,也没关系。

那么,或许某个版本的数据可供使用要比等待所有交易数据都收集齐全更为重要。这与 CAP 中的 A(可用性)相关,它重点关注服务器运行时间。首先必须要知道您能够始终访问数据库,这对提高数据库性能有很大好处(即,可以快速访问文档数据库!)。您会发现,P(分区容忍性)对文档数据库也很重要,尤其是在水平缩放时。

RESTful HTTP API - 主要方式

可以通过 RESTful 方式访问许多 NoSQL 数据库,这样,您就可以通过 URI 建立数据库连接,并且查询和命令都是 HTTP 调用。MongoDB 是个例外。尽管至少还有一个 HTTP API 可用,但其默认设置仍是使用 TCP 进行数据库交互。CouchDB 和 MongoDB 提供特定于语言的 API,以便您可以编写和执行查询和更新,而无需担心直接编写 HTTP 调用的问题。RavenDB 具有一个 .NET 客户端 API,可简化与数据库的交互过程。

单个记录中的相关数据

许多人都错误地认为非关系数据库是平面文件。文档数据库中存储的文档能够包含形状数据:具有节点的树。数据库中的每个记录都是一个文档,并且可以是自治数据集。它是可以自我描述的(包括其可能唯一的架构),并且不必依赖任何其他文档。

下面是某记录在文档数据库中的可能外观的典型示例(我将从 MongoDB 教程中选取表示学生的例子):

{
  "name" : "Jim",
  "scores" : [ 75, 99, 87.2 ]
}

下面是 CouchDB 介绍性文章中用于介绍一本书的示例:

{
  "Subject": "I like Plankton"  
  "Author": "Rusty"  
  "PostedDate": "5/23/2006"  
  "Tags": ["plankton", "baseball", "decisions"]
  "Body": "I decided today that I don't like baseball.
I like plankton."
}

这些是包含字符串数据、数字和数组的简单结构。 您还可在对象内嵌入对象,以获得更复杂的文档结构,比如以下博客文章示例:

{
  "BlogPostTitle”: “LINQ Queries and RavenDB”,
  "Date":"\/Date(1266953391687+0200)\/",
  "Content":”Querying RavenDB is very familiar for .NET developers who are already
    using LINQ for other purposes”,
  "Comments":[
             {
             "CommentorName":"Julie",
             "Date":"\/Date(1266952919510+0200)\/",
             "Text":"Thanks for using something I already know how to
               work with!",
             "UserId":"users/203907"             
             },
  ]
}

唯一键

所有数据库都需要键。 如果您未提供键,它们会在内部为您创建一个键。 键对于数据库的索引功能至关重要,但您自己的域可能要求您有已知键。 在上面的博客文章示例中,请注意,其中存在对“users/203907”的引用。这是 RavenDB 利用键值,并允许您定义文档之间的关系的方式。

以 JSON 格式存储数据

这些示例记录所具有的共同点是它们都使用 JSON 存储其数据。 事实上,CouchDB 和 RavenDB(及许多其他数据库)均采用 JSON 格式存储其数据。 MongoDB 对 JSON 使用称为“二进制 JSON”(BSON) 的转换,以便能够执行二进制序列化。 BSON 是数据的内部表示形式,所以从编程角度看,您应该不会注意到任何区别。

JSON 的简洁性使其很容易将几乎所有语言的对象结构转换为 JSON。 因此,您可在您的应用程序中定义对象,然后将它们直接存储在数据库中。 这样,开发人员便不需要使用对象关系映射程序 (ORM) 不断在数据库架构和类/对象架构之间进行转换。

RavenDB 所依赖的全文搜索引擎(如 Lucene (lucene.apache.org))可提供对此基于文本的数据的高性能搜索。

请注意该博客文章示例中的数据。 JSON 没有数据类型,但每个数据库都提供了一种通过您要用于编码的语言解释数据类型的方式。 如果您查看 MongoDB BSON API 的数据类型和约定列表 (bit.ly/o87Gnx),则会看到添加了一种数据类型及其他一些数据类型,以便充实 JSON 中的可用内容。

在一个单元中存储和检索相关数据可提供显著的性能和可伸缩性优势。 数据库不必四处查找通常相关的数据,因为数据都位于一个位置。

类型的集合

与数据库交互时,应用程序如何知道哪一项代表学生,哪一项代表书,以及哪一项代表博客文章? 数据库使用集合这一概念。 对于与特定集合(如学生集合)关联的任何文档(无论其架构如何),都可在从该集合请求数据时对其进行检索。 使用字段来指示类型也十分常见。 这只是使搜索过程更加轻松,但哪些内容应进入集合,哪些不应进入集合,由您的应用程序决定。

架构灵活的数据库

前面介绍的“学生”包含自己的架构。 每个记录负责自己的架构,甚至负责单个数据库或集合中包含的架构。 并且一个学生记录并不需要与另一学生记录相匹配。 当然,您的软件将需要适应任何差别。 您只需利用此灵活性来提高效率。 例如,为什么存储 null 值? 您可以在属性(如“most_repeated class”)不具有值时执行以下操作:

"name" : "Jim",
"scores" : [ 75, 99, 87.2 ]
"name" : "Julie",
"scores" : [ 50, 40, 65 ],
"most_repeated_class" : "Time Management 101"

是的,Virginia,我们确实支持交易

每个数据库都提供某个级别的交易支持(一些数据库要比其他数据库提供更多支持),但没有哪个数据库可以实现比关系数据库更丰富的支持。 我认同这种看法,让您跟进自己的进一步研究。

文档数据库和领域驱动开发

领域驱动开发的核心概念之一与使用聚合根对域建模相关。 规划域类(可能成为您的数据库中的文档)时,您可以查找通常最为独立的数据(例如具有其明细项的订单),并将其作为单个数据结构加以关注。 在订购系统中,您可能还有客户和产品。 但或许会在不需要订单的客户信息的情况下访问该订单,并且可能会在不需要访问使用产品的订单的情况下使用该产品。 这意味着,尽管您会发现许多机会来包含独立数据结构(如具有其明细项的订单),但这并不表示在某些情形下可以不必或者不通过外键联接数据。

每个数据库都提供各种可用模式的指南,其中指明模式用户可以使用哪些模式获得最大成功。 例如,MongoDB 文档讨论称为“上级数组”的模式,它可加快在联接文档时对相关数据的访问速度。

导航关系问题与这样一个事实相关:在关系数据库中,重复数据是个错误。 对数据库进行标准化可确保不出现此情况。 使用 NoSQL 数据库(尤其是分发数据库)时,对数据进行逆规范化有用且可接受。

查询和更新

每个数据库都附带用于查询和更新的 API。 尽管它们可能不是核心 API 的一部分,但多语言 API 是通过加载项提供的。 作为文档数据库领域的 .NET Framework 条目,RavenDB 使用 LINQ 进行查询 – 这对 .NET 开发人员来说是个很好的优势。

其他查询依赖预定义的视图和称为 Map/Reduce 的模式。 此过程的映射阶段使用这些视图,并且各个数据库的映射职责是不同的。 映射还使数据库能够跨多个处理器分发查询处理。 化简阶段可获取映射查询(如果已分发,则为多个查询)的结果,并将数据聚合到要返回到客户端的结果中。

Map/Reduce 是一种模式,而各种数据库都有自己的实现。 Rob Ashton 在 bit.ly/94OCME 中提供了 RavenDB 和 CouchDB 执行 Map/Reduce 的方式的有趣比较。

尽管 CouchDB 要求您通过预定义的 Map/Reduce 视图进行查询,但 MongoDB(也使用视图和 Map/Reduce)另外提供执行临时查询的功能。 RavenDB 允许使用预定义索引进行查询,但也支持临时查询,并将根据您的实际运行时查询自动为您创建索引。 但在大多数时候,当不采用 SQL 数据库的已知架构和关系本质时,您会丢失的一个功能是执行临时查询的功能。 通过严格控制查询,文档数据库能够实现其快速性能。

数据库变革

许多 非关系数据库都不属于 NoSQL 范畴。 但既然这扇门已经敞开,就会鼓舞更多人去探索可用功能,并考虑如何改进它。 我认为 RavenDB 是这方面的极好示例,Rahien 正在不断考虑如何进一步改进该数据库或者受到用户的启发,您可以通过此过程了解他的改进方式。

我相信对这些数据库的兴趣是具有一定感染力的。 我非常期待进一步探索,以了解更多内容。 但是,即使我已了解的这三个文档数据库也是特别引人注目的,以至于此 Libra 很难在它们之间进行选择,但由于目前我需要解决的是好奇心问题,而不是真正的业务问题,因此关系数据库正好适合我当前的项目。

Julie Lerman 是 Microsoft MVP、.NET 导师和顾问,居住在佛蒙特州的山区。 您可以在全球的用户组和会议中看到她对数据访问和其他 Microsoft .NET 主题的演示。 她是受到广泛称赞的《Programming Entity Framework》(O'Reilly Media,2010)一书的作者,她的博客地址是 thedatafarm.com/blog。 有关他的情况,请访问 Twitter 上的 twitter.com/julielerman

衷心感谢以下技术专家对本文的审阅: Ted Neward Savas Parastatidis