MongoDB特殊的索引和集合

MongoDB学习笔记


一,固定集合

MongoDB中的“普通”集合是动态创建的,而且可以自动增长以容纳更多的数据。MongoDB中还有另一种不同类型的集合,叫做固定集合,固定集合需要事先创建好,而且它的大小是固定的。当固定集合被占满时,如果再插入新文档,固定集合会自动将最老的文档从集合中删除。

不同于普通集合,固定集合必须在使用之前显示创建。在shell中通过createCollection创建。

db.createCollection("test",{'capped':true,'size':10000})

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

db.createCollection("test",{'capped':true,'size':10000,max:100})

即集合中的文档数量限制为100。

固定集合创建之后,就不能改变了,如果需要修改固定集合的属性,只能讲它删除之后再重建。因此,在创建大的固定集合之前应该仔细想清楚它的大小。

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

创建固定集合时还有另一个选项,可以将已有的某个常规集合转换为固定集合,可以使用convertToCapped命令实现。

db.runCommand({'convertToCapped':'test','size':10000})
{
    "ok" : 1.0
}

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


二,自然排序

对固定集合可以进行一种特殊的排序,称为自然排序。自然排序返回结果集中文档的顺序就是文档在磁盘上的顺序。

db.getCollection('test').find({}).sort({'$natural':1})

三,循环游标

循环游标是一种特殊的游标,当循环游标的结果集被取光后,游标不会被关闭。由于循环游标在结果集取光之后不会被关闭,因此,当有新文档插入到集合中时,循环游标会继续取到结果。由于普通集合并不维护文档的插入顺序,所以循环游标只能用在固定集合上。

循环游标通常用于当文档被插入到“工作队列”(其实就是个固定集合)时对新插入的文档进行处理。如果超过10分钟没有新的结果,循环游标就会被释放,因此,当游标被关闭时自动重新执行查询非常重要。


四,TTL索引

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

命令如下:

//超时时间为24小时
db.test.createIndex({lastModifiedDate:1},{expireAfterSeconds:60*60*24})

这样在lastModifiedDate字段上建立一个TTL索引。如果一个文档的lastModifiedDate字段存在并且它的值是日期类型,当服务器时间比文档的lastModifiedDate字段时间晚expireAfterSeconds秒时,文档就会被删除。

为了防止活跃的会话被删除,可以在会话上有活动发生时将lastModifiedDate字段的值更新为当前值。只要lastModifiedDate的时间距离当前时间达到24小时,相应的文档就会被删除。

MongoDB每分钟为TTL索引进行一次清理,所以不应该依赖以秒为单位的时间保证索引的存活状态,可以使用collMod命令修改expireAfterSeconds的值。

db.runCommand({collMod:'test',index: { keyPattern: { lastModifiedDate: 1 },expireAfterSeconds: 3600}})

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


五,全文本索引

MongoDB有一个特殊类型的索引用于在文档中搜索文本。使用正则表达式搜索大块文本的速度非常慢,而且无法处理语言的理解问题,比如entry与entries应该算是匹配的。使用全文本索引可以非常快地进行文本搜索,就如同内置了多种语言分词机制的支持一样。MongoDB全文本索引默认的语言是英语。

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

全文本索引也会导致比“普通”索引更严重的性能问题,因为所有字符串都需要被分解、分词,并且保存到一些地方。因此,可能会发现拥有全文本索引的集合的写入性能比其他集合要差。全文本索引也会降低分片时的数据迁移速度,将数据迁移到其他分片时,所有文本都需要重新进行索引。

在title字段上创建全文本索引

//创建索引
db.test.createIndex({title:'text'})

//导入数据
db.getCollection('test').insert({name:'a',age:1,title:'entries   sdfsdfsdfs'})
db.getCollection('test').insert({name:'a',age:1,title:'entriessdfsdfsdfs'})
db.getCollection('test').insert({name:'a',age:1,title:'你好   sdfsdfsdfs'})
db.getCollection('test').insert({name:'a',age:1,title:'你好sdfsdfsdfs'})
db.getCollection('test').insert({name:'a',age:1,title:'sdfsf java dsfsdf sdf net sfjksdfj web'})
db.getCollection('test').insert({name:'a',age:1,title:'编程   sdfsdfsdfs'})
db.getCollection('test').insert({name:'a',age:1,title:'ABC'})

//搜索
db.test.find( { $text: { $search: "entry 你好 编 abc" } } )
//结果
//能处理语言的理解问题,entry匹配到entries
{
    "_id" : ObjectId("5960aa0ab62da96eff57ab08"),
    "name" : "a",
    "age" : 1.0,
    "title" : "entries   sdfsdfsdfs"
}
//支持中文搜索
{
    "_id" : ObjectId("5960aa0ab62da96eff57ab0a"),
    "name" : "a",
    "age" : 1.0,
    "title" : "你好   sdfsdfsdfs"
}
//搜索英文不区分大小写
{
    "_id" : ObjectId("5960ab1ab62da96eff57ab0e"),
    "name" : "a",
    "age" : 1.0,
    "title" : "ABC"
}

注意:由于英语的基本组成单位就是词,所以分词搜索相对简单。但是,中文文本是由连续的字序列构成,词与词之间是没有天然的分隔符 ,所以中文分词相对来说困难很多。如果一个中文句子是连续的,用其中一个词去搜索,是搜索不到相对应的文档的。

一个集合只能有一个全文本索引,但是全文本索引支持多个字段,并设置权重。默认的权重是1,权重的值范围可以是1~ 99,999。索引一经创建,就不能改变字段的权重了,除非删除索引在重建,所以在生产环境中创建索引之前应该先在测试数据集上实际操作一下。

db.test.createIndex({title:'text',des:'text'},{weights:{title:3,des:2}})

上面的命令会搜索title和des字段中复合条件的文档。

对于某些集合,可能并不知道每个文档所包含的字段。可以使用 **设置权重。

db.test.createIndex({whatever:'text'},{weights:{title:3,'$**':2}})

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

搜索语法
$text 将会使用空格和大部分标点符号作为分隔符对检索字符串进行分词,然后对检索字符串中所有的分词执行一个逻辑的 OR 操作。

db.test.find( { $text: { $search: "java net web" } } )

上面的命令会查询找到所有存储着包含”Java”, “net” 以及 “web” 列表中任何词语的文档。

如果要进行短语的精确匹配,可以用双引号将查询内容括起来

db.test.find( { $text: { $search: "\"java net web\"" } } )

此时,就会查询包含java net web 的文档了。

可以将查询字符串的一部分指定为字面量匹配,另一部分仍然是普通匹配。

db.test.find( { $text: { $search: "\"java net web\" java" } } )

可以使用-字符指定特殊的刺不要出现在搜索结果中。

db.test.find( { $text: { $search: "-web java" } } )

官方文档中对搜索语法的相关介绍
https://docs.mongodb.com/manual/core/text-search-operators/
https://docs.mongodb.com/manual/tutorial/text-search-in-aggregation/


六,地理空间索引

MongoDB支持几种类型的地理空间索引。其中最常用的是2dsphere索引,用于地球表面类型的地图,和2d索引,用于平面地图和时间连续的数据。


七,文件存储系统

MongoDB的存储基本单元BSON文档对象,字段值可以是二进制类型,就像传统关系数据库中的BLOB数据类型。MongoDB可以实现一个存储海量图片、视频、文件资料的分布式文件系统。但这里有个限制,因为MongoDB中的单个BSON对象目前为止最大不能超过16MB,所以如果想要存储大于16MB的文件,就需要用到MongoDB提供的GridFS功能了。
GridFS本质还是建立在MongoDB的基本功能之上的,只不过它会自动分割大文件,形成许多小块,然后将这些小块封装成BSON对象,插入到特意为GridFS准备的集合中。总体来说,MongoDB在实际的应用程序中可以满足两个方面的需求,如果文件都是较小的二进制对象,直接存储在MongoDB数据库中(少数大文件可以在应用程序端分割),如果文件绝大部分都是大文件,那么直接使用MongoDB的GirdFS功能就比较方面。

1,小文件存储

首先考虑有这样一种业务需求,用户可以上传自己的照片、常用的文件(格式如doc、pdf、ppt等不限),其中单个照片、文件绝大部分小于16MB,要能支持大用户量的需求,对于这种需求,直接使用MongoDB的二进制存储功能。
要将一个文件存储到MongoDB中就需要先得到文件对应的二进制值,然后构造一个BSON对象,插入到数据库。
MongoDB可以直接作为一个存储小文件(单个文件小于16MB)的分布式文件系统,重要依赖以下三点:

  • MongoDB可以直接存储二进制数据
  • MongoDB可以部署成分片集群,实现海量数据存储、读写分离
  • 集群中的片可以部署成复制集,保证数据的可靠性

2,GirdFS文件存储

GridFS是MongoDB的一种存储机制,用来存储大型二进制文件,下面列了使用GridFS作为文件存储的理由。

  • 使用GridFS能够简化你的栈,如果已经在使用MongoDB,那么可以使用GridFS来代替独立的文件存储工具。
  • GridFS会自动平衡已有的复制或者为MongoDB设置的自动分片,所以对文件系统做故障转移或者横向扩展会更容易。
  • 当用于存储用户上传的文件时,GridFS可以比较从容地解决其他一些文件系统可能会遇到的问题。例如,在GridFS文件系统中,如果同一个目录下存储大量的文件,没有任何问题,一般操作系统的文件系统会限制一个目录下文件数量。
  • 在GridFS中,文件存储的集合度会比较高,因为MongoDB是以2GB为单位来分配数据文件的。

GridFS也有一些缺点。

  • GridFS的性能比较低,从MongoDB中访问文件,不如直接从文件系统中访问文件速度快。
  • 如果要修改GridFS上的文档,只能先将已有文档删除,然后再将整个文档重新保存。MongoDB将文件作为多个文档进行存储,所以它无法在同一时间对文件的所有块加锁。

通常来说,如果有一些不常改变但是需要经常需要连续访问的大文件,那么使用GridFS再合适不过了。

GridFS 会将大文件对象分割成多个小的chunk(文件片段),一般为256k/个,每个chunk将作为MongoDB的一个文档(document)被存储在chunks集合中。
GridFS 用两个集合来存储一个文件:fs.files与fs.chunks。
每个文件的实际内容被存在chunks(二进制数据)中,和文件有关的meta数据(filename,content_type,还有用户自定义的属性)将会被存在files集合中。

现在使用 GridFS 的 put 命令来存储 测试文件,调用 MongoDB 安装目录下bin的 mongofiles工具。

mongofiles -d test put D:\MongoDB\Server\3.4\data\test.flv

大文件被分割为多个比较大的块,将每个块作为独立的文档进行存储。由于MongoDB支持在文档中存储二进制数据,所以可以将块存储的开销降到非常低。
GridFS中的块会被存储到专用的集合中。块默认使用的集合是fs.chunks,不过可以修改为其他集合。在块集合内容,每个文档的结构非常简单:

{
    "_id" : ObjectId("5960f8f81788d817d4df02d6"),
    "files_id" : ObjectId("5960f8f81788d817d4df02d5"),
    "n" : 0,
    "data" : { "$binary" : (二进制数据)
    }
}

与其他MongoDB文档一样,块也拥有一个唯一的_id 。另外,还有如下几个键。

  • files_id:块所属文件的元信息。
  • n:块在文件中的相对位置。
  • data:块所包含的二进制数据。

每个文件的元信息被保存在一个单独的击中,默认情况下这个集合是fs.files。这个文件集合中的每一个文档表示GridFS中的一个文件,文档中可以包含与这个文件相关的任意用户自定义信息。除用户自定义的键之外,还有几个键是GridFS规范规定必须要有点。

  • _id:文件的唯一id,这个值就是文件的每个块文档中files_id的值。
  • length:文件所包含的字节数。
  • chunkSize:组成文件的每个块的大小,单位是字节。这个值默认是256KB,可以在需要时进行调整。
  • uploadDate:文件被上传到GridFS的日期。
  • md5:文件内容的md5校验值,这个值由服务器端计算得到。
db.getCollection('fs.files').find({})
{
    "_id" : ObjectId("5960f8f81788d817d4df02d5"),
    "chunkSize" : 261120,
    "uploadDate" : ISODate("2017-07-08T15:23:37.849Z"),
    "length" : 26253440,
    "md5" : "11063282510c6d892a20d81f0f578a32",
    "filename" : "D:\\MongoDB\\Server\\3.4\\data\\test.flv"
}

用户可以通过检查文件的md5校验值来确保文件上传正确。
在fs.files中,除了这些必须字段外,可以使用任何自定义的字段来保存必需的文件元信息。可能你希望在文件元信息中保存文件的下载次数、MIME类型或者用户评分。


八,参考资料

菜鸟教程 MongoDB
《大数据存储 MongoDB实战指南》
《MongoDB权威指南》

你可能感兴趣的:(MongoDB)