资讯专栏INFORMATION COLUMN

MongoDB指南---14、特殊的索引和集合:固定集合、TTL索引、全文本索引

cikenerd / 1495人阅读

摘要:固定集合不能被分片。为固定集合指定文档数量限制时,必须同时指定固定集合的大小。没有索引的集合默认情况下,每个集合都有一个索引。

上一篇文章:MongoDB指南---13、索引类型、索引管理
下一篇文章:MongoDB指南---15、特殊的索引和集合:地理空间索引、使用GridFS存储文件

本章介绍MongoDB中一些特殊的集合和索引类型,包括:

用于类队列数据的固定集合(capped collection);

用于缓存的TTL索引;

用于简单字符串搜索的全文本索引;

用于二维平面和球体空间的地理空间索引;

用于存储大文件的GridFS。

 固定集合

MongoDB中的“普通”集合是动态创建的,而且可以自动增长以容纳更多的数据。MongoDB中还有另一种不同类型的集合,叫做固定集合,固定集合需要事先创建好,而且它的大小是固定的(如图6-1所示)。说到固定大小的集合,有一个很有趣的问题:向一个已经满了的固定集合中插入数据会怎么样?答案是,固定集合的行为类似于循环队列。如果已经没有空间了,最老的文档会被删除以释放空间,新插入的文档会占据这块空间(如图6-2所示)。也就是说,当固定集合被占满时,如果再插入新文档,固定集合会自动将最老的文档从集合中删除。

图6-1 新文档被插入到队列末尾

图6-2 如果队列已经被占满,那么最老的文档会被之后插入的新文档覆盖
固定集合的访问模式与MongoDB中的大部分集合不同:数据被顺序写入磁盘上的固定空间。因此它们在碟式磁盘(spinning disk)上的写入速度非常快,尤其是集合拥有专用磁盘时(这样就不会因为其他集合的一些随机性的写操作而“中断”)。

固定集合不能被分片。

固定集合可以用于记录日志,尽管它们不够灵活。虽然可以在创建时指定集合大小,但无法控制什么时候数据会被覆盖。

 创建固定集合

不同于普通集合,固定集合必须在使用之前先显式创建。可以使用create命令创建固定集合。在shell中,可以使用createCollection函数:

> db.createCollection("my_collection", {"capped" : true, "size" : 100000});
{ "ok" : true }

上面的命令创建了一个名为my_collection大小为100 000字节的固定集合。
除了大小,createCollection还能够指定固定集合中文档的数量:

> db.createCollection("my_collection2",
... {"capped" : true, "size" : 100000, "max" : 100});
{ "ok" : true }

可以使用这种方式来保存最新的10则新闻,或者是将每个用户的文档数量限制为1000。
固定集合创建之后,就不能改变了(如果需要修改固定集合的属性,只能将它删除之后再重建)。因此,在创建大的固定集合之前应该仔细想清楚它的大小。

为固定集合指定文档数量限制时,必须同时指定固定集合的大小。不管先达到哪一个限制,之后插入的新文档就会把最老的文档挤出集合:固定集合的文档数量不能超过文档数量限制,固定集合的大小也不能超过大小限制。

创建固定集合时还有另一个选项,可以将已有的某个常规集合转换为固定集合,可以使用convertToCapped命令实现。下面的例子将test集合转换为一个大小为10 000字节的固定集合:

> db.runCommand({"convertToCapped" : "test", "size" : 10000});
{ "ok" : true }

无法将固定集合转换为非固定集合(只能将其删除)。

 自然排序

对固定集合可以进行一种特殊的排序,称为自然排序(natural sort)。自然排序返回结果集中文档的顺序就是文档在磁盘上的顺序(如图6-3所示)。

图6-3 使用{"$natural" : 1}进行排序
对大多数集合来说,自然排序的意义不大,因为文档的位置经常变动。但是,固定集合中的文档是按照文档被插入的顺序保存的,自然顺序就是文档的插入顺序。因此,自然排序得到的文档是从旧到新排列的。当然也可以按照从新到旧的顺序排列(如图6-4所示)。

> db.my_collection.find().sort({"$natural" : -1})

图6-4 使用{"$natural" : -1}进行排序

循环游标

循环游标(tailable cursor)是一种特殊的游标,当循环游标的结果集被取光后,游标不会被关闭。循环游标的灵感来自tail -f命令(循环游标跟这个命令有点儿相似),会尽可能久地持续提取输出结果。由于循环游标在结果集取光之后不会被关闭,因此,当有新文档插入到集合中时,循环游标会继续取到结果。由于普通集合并不维护文档的插入顺序,所以循环游标只能用在固定集合上。
循环游标通常用于当文档被插入到“工作队列”(其实就是个固定集合)时对新插入的文档进行处理。如果超过10分钟没有新的结果,循环游标就会被释放,因此,当游标被关闭时自动重新执行查询是非常重要的。下面是一个在PHP中使用循环游标的例子(不能在mongo shell中使用循环游标):

$cursor = $collection->find()->tailable();

while (true) {
    if (!$cursor->hasNext()) {
        if ($cursor->dead()) {
            break;
        }
        sleep(1);
    }
    else {
        while ($cursor->hasNext()) {
            do_stuff($cursor->getNext());
        }
    }
}

这个游标会不断对查询结果进行处理,或者是等待新的查询结果,直到游标被关闭(超过10分钟没有新的结果或者人为中止查询操作)。

没有_id索引的集合

默认情况下,每个集合都有一个"_id"索引。但是,如果在调用createCollection创建集合时指定autoIndexId选项为false,创建集合时就不会自动在"_id"上创建索引。实践中不建议这么使用,但是对于只有插入操作的集合来说,这确实可以带来速度的稍许提升。

如果创建了一个没有"_id"索引的集合,那就永远都不能复制它所在的mongod了。复制操作要求每个集合上都要有"_id"索引(对于复制操作,能够唯一标识集合中的每一个文档是非常重要的)。

在2.2版本之前,固定集合默认是没有"_id"索引的,除非显式地将autoIndexId置为true。如果正在使用旧版的固定集合,要确保你的应用程序能够填充"_id"字段(大多数驱动程序会自动填充"_id"字段),然后使用ensureIndex命令创建"_id"索引。
记住,"_id"索引必须是唯一索引。不同于其他索引,"_id"索引一经创建就无法删除了,因此在生产环境中创建索引之前先自己实践一下是非常重要的。所以创建"_id"索引必须一次成功!如果创建的"_id"索引不合规范,就只能删除集合再重建了。

 TTL索引

上一节已经讲过,对于固定集合中的内容何时被覆盖,你只拥有非常有限的控制权限。如果需要更加灵活的老化移出系统(age-out system),可以使用TTL索引(time-to-live index,具有生命周期的索引),这种索引允许为每一个文档设置一个超时时间。一个文档到达预设置的老化程度之后就会被删除。这种类型的索引对于缓存问题(比如会话的保存)非常有用。
在ensureIndex中指定expireAfterSecs选项就可以创建一个TTL索引:

> // 超时时间为24小时
> db.foo.ensureIndex({"lastUpdated" : 1}, {"expireAfterSecs" : 60*60*24})

这样就在"lastUpdated"字段上建立了一个TTL索引。如果一个文档的"lastUpdated"字段存在并且它的值是日期类型,当服务器时间比文档的"lastUpdated"字段的时间晚expireAfterSecs秒时,文档就会被删除。
为了防止活跃的会话被删除,可以在会话上有活动发生时将"lastUpdated"字段的值更新为当前时间。只要"lastUpdated"的时间距离当前时间达到24小时,相应的文档就会被删除。
MongoDB每分钟对TTL索引进行一次清理,所以不应该依赖以秒为单位的时间保证索引的存活状态。可以使用collMod命令修改expireAfterSecs的值:

> db.runCommand({"collMod" : "someapp.cache", "expireAfterSecs" : 3600})

在一个给定的集合上可以有多个TTL索引。TTL索引不能是复合索引,但是可以像“普通”索引一样用来优化排序和查询。

全文本索引

MongoDB有一个特殊类型的索引用于在文档中搜索文本。前面几章都是使用精确匹配和正则表达式来查询字符串,但是这些技术有一些限制。使用正则表达式搜索大块文本的速度非常慢,而且无法处理语言的理解问题(比如entry与entries应该算是匹配的)。使用全文本索引可以非常快地进行文本搜索,就如同内置了多种语言分词机制的支持一样。

创建任何一种索引的开销都比较大,而创建全文本索引的成本更高。在一个操作频繁的集合上创建全文本索引可能会导致MongoDB过载,所以应该是离线状态下创建全文本索引,或者是在对性能没要求时。创建全文本索引时要特别小心谨慎,内存可能会不够用(除非你有SSD)。

全文本索引也会导致比“普通”索引更严重的性能问题,因为所有字符串都需要被分解、分词,并且保存到一些地方。因此,可能会发现拥有全文本索引的集合的写入性能比其他集合要差。全文本索引也会降低分片时的数据迁移速度:将数据迁移到其他分片时,所有文本都需要重新进行索引。
写作本书时,全文本索引仍然只是一个处于“试验阶段”的功能,所以需要专门启用这个功能才能进行使用。启动MongoDB时指定--setParameter textSearch Enabled=true选项,或者在运行时执行setParameter命令,都可以启用全文本索引:

> db.adminCommand({"setParameter" : 1, "textSearchEnabled" : true})

假如我们使用这个非官方的Hacker News JSON API(http://api.ihackernews.com)将最近的一些文章加载到了MongoDB中。
为了进行文本搜索,首先需要创建一个"text"索引:

> db.hn.ensureIndex({"title" : "text"})

现在,必须通过text命令才能使用这个索引(写作本书时,全文本索引还不能用在“普通”查询中):

test> db.runCommand({"text" : "hn", "search" : "ask hn"})
{
    "queryDebugString" : "ask|hn||||||",
    "language" : "english",
    "results" : [
        {
            "score" : 2.25,
            "obj" : {
                "_id" : ObjectId("50dcab296803fa7e4f000011"),
                "title" : "Ask HN: Most valuable skills you have?",
                "url" : "/comments/4974230",
                "id" : 4974230,
                "commentCount" : 37,
                "points" : 31,
                "postedAgo" : "2 hours ago",
                "postedBy" : "bavidar"
            }
        },
        {
            "score" : 0.5625,
            "obj" : {
                "_id" : ObjectId("50dcab296803fa7e4f000001"),
                "title" : "Show HN: How I turned an old book...",
                "url" : "http://www.howacarworks.com/about",
                "id" : 4974055,
                "commentCount" : 44,
                "points" : 95,
                "postedAgo" : "2 hours ago",
                "postedBy" : "AlexMuir"
            }
        },
        {
            "score" : 0.5555555555555556,
            "obj" : {
                "_id" : ObjectId("50dcab296803fa7e4f000010"),
                "title" : "Show HN: ShotBlocker - iOS Screenshot detector...",
                "url" : "https://github.com/clayallsopp/ShotBlocker",
                "id" : 4973909,    
                "commentCount" : 10,
                "points" : 17,
                "postedAgo" : "3 hours ago",
                "postedBy" : "10char"
        }
    }
],
"stats" : {
    "nscanned" : 4,
    "nscannedObjects" : 0,
    "n" : 3,
    "timeMicros" : 89
},
"ok" : 1 } 

匹配到的文档是按照相关性降序排列的:"Ask HN"位于第一位,然后是两个部分匹配的文档。每个对象前面的"score"字段描述了每个结果与查询的匹配程度。
如你所见,这个搜索是不区分大小写不的,至少对于[a-zA-Z]这些字符是这样。全文本索引会使用toLower将单词变为小写,但这是与本地化相关的,所以某些语言的用户可能会发现MongoDB会不可预测性地变得区分大小写,这取决于toLower在不同字符集上的行为。MongoDB一直在努力提高对不同字符集的支持。
全文本索引只会对字符串数据进行索引:其他的数据类型会被忽略,不会包含在索引中。一个集合上最多只能有一个全文本索引,但是全文本索引可以包含多个字段:

> db.blobs.ensureIndex({"title" : "text", "desc" : "text", "author" : "text"})

与“普通”的多键索引不同,全文本索引中的字段顺序不重要:每个字段都被同等对待。可以为每个字段指定不同的权重来控制不同字段的相对重要性:

> db.hn.ensureIndex({"title" : "text", "desc" : "text", "author" : "text"},
... {"weights" : {"title" : 3, "author" : 2}})

默认的权重是1,权重的范围可以是1~1 000 000 000。使用上面的代码设置权重之后,"title"字段成为其中最重要的字段,"author"其次,最后是"desc"(没有指定,因此它的权重是默认值1)。
索引一经创建,就不能改变字段的权重了(除非删除索引再重建),所以在生产环境中创建索引之前应该先在测试数据集上实际操作一下。
对于某些集合,我们可能并不知道每个文档所包含的字段。可以使用"$**"在文档的所有字符串字段上创建全文本索引:这不仅会对顶级的字符串字段建立索引,也会搜索嵌套文档和数组中的字符串字段:

> db.blobs.ensureIndex({"$**" : "text"})

也可以为"$**"设置权重:

> db.hn.ensureIndex({"whatever" : "text"},
... {"weights" : {"title" : 3, "author" : 1, "$**" : 2}})

"whatever"可以指代任何东西。在设置权重时指明了是对所有字段进行索引,因此MongoDB并不要求你明确给出字段列表。

 搜索语法

默认情况下,MongoDB会使用OR连接查询中的每个词:“ask OR hn”。这是执行全文本查询最有效的方式,但是也可以进行短语的精确匹配,以及使用NOT。为了精确查询“ask hn”这个短语,可以用双引号将查询内容括起来:

> db.runCommand({text: "hn", search: ""ask hn""})
{
    "queryDebugString" : "ask|hn||||ask hn||",
    "language" : "english",
    "results" : [
        {
            "score" : 2.25,
            "obj" : {
                "_id" : ObjectId("50dcab296803fa7e4f000011"),
                "title" : "Ask HN: Most valuable skills you have?",
                "url" : "/comments/4974230",
                "id" : 4974230,
                "commentCount" : 37,
                "points" : 31,
                "postedAgo" : "2 hours ago",
                "postedBy" : "bavidar"
            }
        }
    ],
    "stats" : {
        "nscanned" : 4,
        "nscannedObjects" : 0,
        "n" : 1,
        "nfound" : 1,
        "timeMicros" : 20392
    },
    "ok" : 1
}

这比使用OR的匹配慢一些,因为MongoDB首先要执行一个OR匹配,然后再对匹配结果进行AND匹配。
可以将查询字符串的一部分指定为字面量匹配,另一部分仍然是普通匹配:

> db.runCommand({text: "hn", search: ""ask hn" ipod"})

这会精确搜索"ask hn"这个短语,也会可选地搜索"ipod"。
也可以使用"-"字符指定特定的词不要出现在搜索结果中:

> db.runCommand({text: "hn", search: "-startup vc"})

这样就会返回匹配“vc”但是不包含“startup”这个词的文档。

 优化全文本搜索

有几种方式可以优化全文本搜索。如果能够使用某些查询条件将搜索结果的范围变小,可以创建一个由其他查询条件前缀和全文本字段组成的复合索引:

> db.blog.ensureIndex({"date" : 1, "post" : "text"})

这就是局部的全文本索引,MongoDB会基于上面例子中的"date"先将搜索范围分散为多个比较小的树。这样,对于特定日期的文档进行全文本查询就会快很多了。
也可以使用其他查询条件后缀,使索引能够覆盖查询。例如,如果要返回"author"和"post"字段,可以基于这两个字段创建一个复合索引:

> db.blog.ensureIndex({"post" : "text", "author" : 1})

前缀和后缀形式也可以组合在一起使用:

> db.blog.ensureIndex({"date" : 1, "post" : "text", "author" : 1})

这里的前缀索引字段和后缀索引字段都不可以是多键字段。
创建全文本索引会自动在集合上启用usePowerOf2Sizes选项,这个选项可以控制空间的分配方式。这个选项能够提高写入速度,所以不要禁用它。

 在其他语言中搜索

当一个文档被插入之后(或者索引第一次被创建之后),MongoDB会查找索引字段,对字符串进行分词,将其减小为一个基本单元(essential unit)。然后,不同语言的分词机制是不同的,所以必须指定索引或者文档使用的语言。文本类型的索引允许指定"default_language"选项,它的默认值是"english",可以被设置为多种其他语言(MongoDB的在线文档提供了最新的支持语言列表)。
例如,要创建一个法语的索引,可以这么做:

> db.users.ensureIndex({"profil" : "text", "intérêts" : "text"},
... {"default_language" : "french"})

这样,这个索引就会默认使用法语的分词机制,除非指定了其他的分词机制。如果在插入文档时指定"language"字段,就可以为每个文档分别指定分词时使用的语言:

> db.users.insert({"username" : "swedishChef",
... "profile" : "Bork de bork", language : "swedish"})
上一篇文章:MongoDB指南---13、索引类型、索引管理
下一篇文章:MongoDB指南---15、特殊的索引和集合:地理空间索引、使用GridFS存储文件

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/19577.html

相关文章

  • MongoDB指南---14特殊索引集合固定集合TTL索引全文索引

    摘要:固定集合不能被分片。为固定集合指定文档数量限制时,必须同时指定固定集合的大小。没有索引的集合默认情况下,每个集合都有一个索引。 上一篇文章:MongoDB指南---13、索引类型、索引管理下一篇文章:MongoDB指南---15、特殊的索引和集合:地理空间索引、使用GridFS存储文件 本章介绍MongoDB中一些特殊的集合和索引类型,包括: 用于类队列数据的固定集合(capped...

    QLQ 评论0 收藏0
  • MongoDB指南---13、索引类型、索引管理

    摘要:复合唯一索引也可以创建复合的唯一索引。中的稀疏索引与关系型数据库中的稀疏索引是完全不同的概念。但是这里不会指明索引是否是多键索引。上一篇文章指南使用和何时不应该使用索引下一篇文章指南特殊的索引和集合固定集合索引全文本索引 上一篇文章:MongoDB指南---12、使用explain()和hint()、何时不应该使用索引下一篇文章:MongoDB指南---14、特殊的索引和集合:固定集合...

    Enlightenment 评论0 收藏0
  • MongoDB指南---13、索引类型、索引管理

    摘要:复合唯一索引也可以创建复合的唯一索引。中的稀疏索引与关系型数据库中的稀疏索引是完全不同的概念。但是这里不会指明索引是否是多键索引。上一篇文章指南使用和何时不应该使用索引下一篇文章指南特殊的索引和集合固定集合索引全文本索引 上一篇文章:MongoDB指南---12、使用explain()和hint()、何时不应该使用索引下一篇文章:MongoDB指南---14、特殊的索引和集合:固定集合...

    seanHai 评论0 收藏0
  • MongoDB指南---15、特殊索引集合:地理空间索引、使用GridFS存储文件

    摘要:下面列出了使用作为文件存储的理由。如果已经在使用,那么可以使用来代替独立的文件存储工具。在中,文件存储的集中度会比较高,因为是以为单位来分配数据文件的。揭开的面纱是一种轻量级的文件存储规范,用于存储中的普通文档。 上一篇文章:MongoDB指南---14、特殊的索引和集合:固定集合、TTL索引、全文本索引下一篇文章:MongoDB指南---16、聚合 地理空间索引 MongoDB支持...

    mtunique 评论0 收藏0
  • MongoDB指南---15、特殊索引集合:地理空间索引、使用GridFS存储文件

    摘要:下面列出了使用作为文件存储的理由。如果已经在使用,那么可以使用来代替独立的文件存储工具。在中,文件存储的集中度会比较高,因为是以为单位来分配数据文件的。揭开的面纱是一种轻量级的文件存储规范,用于存储中的普通文档。 上一篇文章:MongoDB指南---14、特殊的索引和集合:固定集合、TTL索引、全文本索引下一篇文章:MongoDB指南---16、聚合 地理空间索引 MongoDB支持...

    2i18ns 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<