在开始了解如何使用和生成视图之前,首先来了解一下如何以及何时不使用视图。视图是在 Cloudant 中完成各项工作的有效方法,但其中有些缺点须牢记于心,主要包括:必须通过索引流程来构建视图,无论是创建还是更改视图都是如此;视图会占用空间;视图不一定是数据检索的最有效途径。

在创建或更新设计文档时,必须重新构建该文档中定义的所有视图和索引。发生这种情况时,Cloudant 会迭代数据库中的每个文档,在每个文档上,它会为设计文档中的每个视图运行映射函数,然后保存为每个视图生成的键和值。创建或更新文档时,都仅为此文档运行并更新所有数据库的视图。保存的结果可作为调用视图时的查询对象,调用视图速度非常之快的原因就在于此。此帖提供了有关索引构建的更多信息,但主要须牢记的是,如果数据库有大量文档或者视图有复杂的映射函数,那么此过程可能很耗时,并且操作起来耗费大量成本。您还可查看 设计文档管理指南,进一步深入了解如何优化设计文档。

除非您要处理的数据库非常庞大,或者要将大量数据发送到视图中,否则任一视图所占的空间都不会是一个太大的问题,但最好能够在不占用额外存储空间的情况下完成相同目标。您可使用设计文档的 信息端点 来查看视图占用的存储空间。

视图是一种辅助索引类型,因此几乎在任何情况下,使用主索引都更为有效。Cloudant 的主索引以文档的“_id”字段作为键值。视图相比于主索引,更便于您自由操作,并且提供的功能更多,但巧妙利用文档标识通常使您能够在某些起初预计要使用辅助索引的场合中改为使用主索引。如果您正在思考如何实施,请记住以下几点:

  • 查找(通过文档“_id”来获取文档)相比于视图和搜索调用速度更快且效率更高。对于 IBM Cloud 上的多租户 Cloudant,它们还更实惠
  • The all_docs 端点可访问主索引,并可通过使用 startkey 和 endkey 来调用,从而检索所有文档的子集。对于 IBM Cloud 定价,调用 _all_docs 端点计作为一次查询(使用视图也是如此),但由于它是内置功能,因此比自定义视图更为优化。并且,由于它基于主索引,Cloudant 必须执行的必要索引无可避免,因此不会创建额外索引流程,也不会占用额外的存储空间。

请牢记以上要点,让我们来看两个示例,它们展示了精心构建的“_id”如何用于替换视图。第一个案例显示的示例文档对应于具有随机生成的标识和三个视图的数据库。第二个案例显示的示例文档对应于相同的数据库,但标识经过构造,可将视图替换为调用 _all_docs。

对于这两个案例,我们将查看包含以下三种类型文档的“Pets”数据库:category、breed 和 food。Category 文档包含有关广泛宠物类别的信息,例如,狗、猫、鱼等。Breed 文档包含有关每个类别中的特定品种的信息。Food 文档包含不同品种所食用的宠物食物的信息。用户/应用需要能够访问所有类别的列表、某个特定类别的文档、某个类别中所有品种的列表,以及某个品种所食用的所有食物的列表。以下示例文档展示了不同文档类型的模式,以及用户/应用如何从这些文档中获取所需的数据。

第一个案例:不使用“_id”

对于第一个案例,使用视图而不使用“_id”字段的数据库如下所示:

示例文档:

种类:

品种:

食物:

设计文档:


每个视图的用例:

  • “categories”:获取所有类别;拉取特定类别的文档(使用 key=<category>&include_docs=true) 调用)
  • “breed-by-category”:获取某个类别的所有品种(使用 key=<category>) 调用)
  • “food-by-breed”:获取某个品种的所有食物(使用 key=<breed>) 调用)

第二个案例:使用“_id”替换视图

对于第二个案例,使用“_id”字段替换视图的同一个数据库如下所示:

以下模板将用于为每个文档类型构造文档标识:

  • category – “category:<category name>”
  • breed – “breed:<category name>:<breed name>”
  • food – “food:<category name>:<breed name>:<food name>”
示例文档:

种类:

品种:

食物:

替换每个用例的视图:

  • 获取所有类别:/_all_docs?startkey="category:"&endkey="category;"
  • 拉取特定类别的文档(使用查找代替视图,这将降低成本并提高效率):/category:<category>
  • 获取某个类别的所有品种:/_all_docs?startkey="breed:<category>:"&endkey="breed:<category>;"
  • 获取某个品种的所有食物:/_all_docs?startkey="food:<category>:<breed>:"&endkey="food:<category>:<breed>;"

(请注意,每个案例中的 endkey 均以分号而不是冒号结尾。)这是因为_all_docs 使用 ASCII 排序对文档标识进行排序,而在 ASCII 中,“;”位于“:”之后。)

以上设置假定每种宠物食物仅供一个品种食用。如果情况并非如此,那么可能就需要采用视图,并且文档可能如下所示:

食物:

设计文档:

以上设计文档中的视图循环通过食物文档中的“breed”数组,并将每个品种作为一个键发出,使用食物名称作为其值。要获取某个品种的所有食物,可使用 key=<breed> 查询“food-by-breed”视图。

希望这些示例能够帮助您思考以最佳方式使用 Cloudant 的主索引来访问数据的方法。现在,我们可以继续探讨在决定确实需要采用视图时要考量的事项。

哪些内容应“发送”到视图中?

为视图编写 javascript 映射功能时,它将向该视图“发送”一个键和值。键和值可以是任何数据类型,包括 null。查询视图时,将收到一组对象,其中每个对象都包含一个“key”字段、一个“value”字段和一个“id”字段(其中包含键和值来自的文档的“_id”)。您选择在视图中发送的键和值将与该视图保存在一起,并相应增加该视图所占用的空间;因此,通常情况下,最好仅从视图发送所需的数据。有鉴于此,以下是决定要发送到视图的内容时运用的一些经验法则:

无需发送“_id”

文档标识已保存为视图的一部分,因此通常没有理由同时发送标识。如果出现只需来自视图的“_id”字段的情况,可以发出 (null, null)。在某些情况下,您可能有兴趣在标识上使用“startkey”和“endkey”,或者尝试为某些标识做减法,即希望使用标识作为视图中的键。除此之外,通常不需要标识。

但有时候, emit(some_key, {"_id": "<id of a DIFFERENT doc>") 会很有用,因为在此情况下使用 include_docs=true 时,Cloudant 将返回完整文档,并在值中包含“_id”,而不是返回该行所来自的文档。

您(可能)不应发送整个文档

如果您在视图中通常只需来自文档的部分数据,那么只需发送您感兴趣的数据。调用视图时,您可将 include_docs 查询参数设置为 true,以便检索完整文档以及标识、键和值,因此,通常无需将整个文档发送到视图中。在视图中包含整个文档会占用更多空间,并且会减缓视图查询速度,因为发送的数据更多。因此,使用 include_docs=true 相比于发送文档略慢,因为必须对每个文档执行查找,而不是视图中已存在文档。

如果每次调用视图时,针对视图中的每一行都将需要访问整个文档,并且无须担心视图所占用的存储空间量(例如,数据库相对较小或者视图中行数较少),那么可以发送整个文档。这将在视图中保存一份文档副本,虽然这样会占用额外空间,但检索速度更快。

了解视图排序,选择实用的键

根据键和/或使用 startkey 和 endkey 来查询视图是一种便捷的数据访问方式,因此,选择实用的键对于许多视图用例都非常重要。例如,可将日期/时间值作为键发出,并使用 startkey/endkey 来检索某个日期/时间范围内的数据。您还可构建复杂键(例如,包含时间和数据类型的数组),以便检索一段时间内的某种数据类型。如果计划对视图使用 startkey/endkey,请务必查看有关视图排序的文档,以便了解 Cloudant 所基于的 CouchDB 如何对视图中的行进行排序。

适用于 Cloudant 视图的其他资源

 

本文翻译自:A guide to writing (and avoiding) views in Cloudant(2018-8-24)

加入讨论