MongoDB对文档索引提供了广泛的支持,让我们可以非常方便的构建索引。没有索引,数据库就必须按序遍历所有文档,检查它们是否符合查询条件。这就像是在菜鸟驿站中取快递时,我们需要堆积如山的快递中逐一寻找。在使用索引系统之后(驿站人员会按区域编号,区域内的快递也会按序排列),我们就可以到对应的区域里面,根据有序的编号轻松地找到快递。
虽然索引可以帮助我们提高查询的性能,但是相对的也会降低插入/删除的性能。所以应该根据读写操作的比例(尤其是在写场景较多的情况下),合理的构建索引。
索引类型
单索引
MongoDB可以针对文档中特定的字段构建简单索引(默认情况下,MongoDB根据_id字段自动创建索引)。
例如,我们根据书籍名称,按照升序排序规则创建索引:
db.books.createIndex({ name: 1 })
如果希望按照降序排序规则,只需要做一点小改动即可:
db.books.createIndex({ name: - 1 })
为嵌入式文档中的字段创建索引,可以使用 . 的形式引用对应属性。例如,子文档press中有一个name字段,那么我们可以使用下面的操作创建索引:
db.books.createIndex({ press.name: 1 })
如果被指定的字段是数组类型,那么该索引会将数组中的所有元素作为不同的索引条目添加到其中,称为多键索引。
复合索引
MongoDB允许将多字段结合在一起创建一个索,它提供了一种减少集合中索引数量的方法。多字段索引相比单字段索引,需要更多的内存,所以需要慎重考虑是否在索引中包含更多的字段。
例如,可以针对书名和书编号创建一个索引:
db.books.createIndex({ name: 1, isbn: 1 })
需要注意的是,排序并不擅长使用复合索引,除非排序数据项的列表和排序与索引结构相同。例如:
db.books.find().sort({ name: -1, isbn: 1 })
就无法使用上文中创建的索引,因为查询中的排序规则和已有索引排序规则产生冲突。
另外一个需要重点关注问题,就是MongoDB查询优化器应用索引时匹配规则 -- “最左匹配原则”。比如,执行上文中提到的命令,将会生成一个名为name\_1\_isbn\_1的索引。该索引可以适用于如下的查询:
db.books.find({ name: 'aric' })
db.books.find({ name: 'aric', isbn: '2024' })
db.books.find({ isbn: '2024', name: 'aric' })
以下查询则无法使用索引,因为不满足最左匹配原则:
db.books.find({ isbn: '2024' })
注意
创建复合索引时,需要根据查询结构调整字段的顺序,一般建议把最频繁使用的字段放在最左边。如果查询条件中包含索引中的所有字域,则字段出现的顺序不影响索引匹配。
全文索引
MongoDB中可以通过创建文本索引来是实现全文搜索。
例如,我们可以为book集合中的summary字段创建文本索引:
db.books.createIndex({ summary: 'text' })
重要
每个集合只能创建一个文本索引
运行文本搜索:
db.books.find({ $text: { $search: 'transmission control protocal' } })
MongoDB会对搜索文本进行分析提取词干。然后将这些词干与每个文档进行比较,计算相对得分。最后基于得分将文档返回给用户。如果想要查看每个文档的得分情况,可以使用$meta操作符来返回与文档关联的得分:
db.books.find({ $text: { $search: 'transmission control protocal' } }, { score: { $meta: 'textScore' }})
指定文本默认语言:
db.books.find({ $text: { $search: 'transmission control protocal' } }, { default_language: 'en' })
进行字符串字面量的搜索,可以搜索文本两端添加双引号:
db.books.find({ $text: { $search: '"transmission control protocal"' } })
文本索引也可以创建复合索引,例如:
db.books.createIndex({ name: 1, summary: 'text'})
db.books.createIndex({ summary: 'text', description: 'text' })
在多字段上创建文本复合索引时,还可以通过weights选项指定字段搜索权重。例如:
{ weights: { summary: 10, description: 5 } }
唯一索引
在创建索引时,使用{ unique: true }选项可以创建唯一索引。例如:
db.books.createIndex({ isbn: 1 }, { unique: true })
注意
唯一索引要求所有的键都必须不同。如果尝试插入一个文档,并且该文档中的键与现有文档中的键相同,MongoDB会返回错误。
如果在创建文档时缺少唯一键的字段,MongoDB将自动插入该字段并设置为null。所以无法再次插入一条缺少唯一键的字段的文档,因为已经存在一个键为null的文档了。
TTL索引
如果希望在某个时间节点之后,MongoDB自动删除旧文档,那么可以为该文档创建一个TTL索引。例如:
db.books.createIndex({ createdAt: 1 }, { expireAfterSeconds: 60 })
注意
被索引的字段必须是BSON日期类型,否则文档将不会被删除。
稀疏索引
只为具有某个字段的文档创建索引,这是一种比较节省内存的机制,因为只对有效的文档进行索引。例如:
db.books.createIndex({ discountedPrize: 1 }, { sparse: true })
如果在查询时使用了稀疏索引,可能会找不到匹配的文档,因为稀疏索引不会总是包含每个文档。例如:
db.books.find({ discountedPrize: null, prize: 99 }).hint('discountedPrize_1')
虽然集合中有prize值为99的书籍,但是上面查询不会返回匹配的文档(这里为了说明问题,强制mongoDB使用特定的索引)。
部分索引
部分索引和稀疏索引一样,只包含匹配给定条件的文档。例如,只给价格超过100元的书籍创建索引:
db.books.createIndex({ name: 1 }, { partialFilterExpression: { prize: { $gt: 100 } } })
*hint()函数
MongoDB查询优化器会从所有的索引中选择一个它认为“最优的”索引用于查询。但是,有时候查询优化器所作出的选择对于当下情况来说未必是正确的。因此,MongoDB提供了一种手段来强制查询优化器使用特定的索引。例如:
db.books.find({ authors: 'aric' }).hint('authors_1')
关于索引方面更加详细的资料,可以参考Mongo官方文档。