MongoDB学习(三):索引

目录

1.普通索引

1)索引建立、删除

2)覆盖索引查询

3)索引失效

4)索引限制

2.全文索引


1.普通索引

索引是加速数据库查询的重要工具,只有在数据量比较大的时候才有意义,所以向数据库中批量插入20000条数据:

> for(i=0;i<20000;i++){
... db.numbers.save({num:i});
... }
WriteResult({ "nInserted" : 1 })
> db.numbers.count()
20000

执行后需要稍微等待几秒钟

做一次查询并explain(显示查询计划),结果类似下面:

> db.numbers.find({num:{"$gt":19995}}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "test.numbers",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "num" : {
                                "$gt" : 19995
                        }
                },
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "num" : {
                                        "$gt" : 19995
                                }
                        },
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 4,
                "executionTimeMillis" : 10,
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 20000,
                "executionStages" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "num" : {
                                        "$gt" : 19995
                                }
                        },
                        "nReturned" : 4,
                        "executionTimeMillisEstimate" : 11,
                        "works" : 20002,
                        "advanced" : 4,
                        "needTime" : 19997,
                        "needYield" : 0,
                        "saveState" : 156,
                        "restoreState" : 156,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "direction" : "forward",
                        "docsExamined" : 20000
                }
        },
        "serverInfo" : {
                //这里是一些本机信息
        },
        "ok" : 1
}

explain函数可以接受的参数有:'queryPlanner'(等于无参), 'executionStats', 'allPlansExecution'

这里可以看到,本次查询需要遍历全部20000个文档(docsExamined),耗时大约10ms,在真实环境中,NoSQL数据库存储的数据量级远远大于2W,假如200W数据,那就要1s,显然不可接受

1)索引建立、删除

可以使用createIndex函数,在这上面建立索引:

> db.numbers.createIndex({num:1})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}
> db.numbers.getIndexes()
[
        {
                "v" : 2,
                "key" : {
                        "_id" : 1
                },
                "name" : "_id_",
                "ns" : "test.numbers"
        },
        {
                "v" : 2,
                "key" : {
                        "num" : 1
                },
                "name" : "num_1",
                "ns" : "test.numbers"
        }
]

num:1代表在num域建立索引,使用getIndexes()查询可得,目前有两个索引,一个关联在_id,一个关联在num,如果要删除索引,可以使用dropIndex函数,用法和createIndex一致,不过不能删除_id上的索引。

也可以用ensureIndex命令建立索引,不过更建议使用createIndex,这个API更新。

createIndex函数也可以接受选项:

参数

类型

描述

background

Boolean

建索引过程会阻塞其它数据库操作,background可指定以后台方式创建索引,即增加 "background" 可选参数。 "background" 默认值为false

unique

Boolean

建立的索引是否唯一。指定为true创建唯一索引。默认值为false.

name

string

索引的名称。如果未指定,MongoDB的通过连接索引的字段名和排序顺序生成一个索引名称。

dropDups

Boolean

在建立唯一索引时是否删除重复记录,指定 true 创建唯一索引。默认值为 false.

sparse

Boolean

是否启用稀疏索引;这个参数需要特别注意,如果设置为true的话,在索引字段中不会查询出不包含对应字段的文档.。默认值为 false.即默认使用密集索引。

expireAfterSeconds

integer

指定一个以秒为单位的数值,完成 TTL设定,设定集合的生存时间。

v

index version

索引的版本号。默认的索引版本取决于mongod创建索引时运行的版本。

weights

document

索引权重值,数值在 1 到 99,999 之间,表示该索引相对于其他索引字段的得分权重。

default_language

string

对于文本索引,该参数决定了停用词及词干和词器的规则的列表。 默认为英语

language_override

string

对于文本索引,该参数指定了包含在文档中的字段名,语言覆盖默认的language,默认值为 language.

需要注意的是,如果要建立唯一索引,最好在插入数据前就进行,否则可能会出现冲突。

重新explain刚刚的查询,"executionStages"一节如下:

                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 4,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 5,
                        "advanced" : 4,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "docsExamined" : 4,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 4,
                                "executionTimeMillisEstimate" : 0,
                                "works" : 5,
                                "advanced" : 4,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 0,
                                "restoreState" : 0,
                                "isEOF" : 1,
                                "invalidates" : 0,
                                "keyPattern" : {
                                        "num" : 1
                                },
                                "indexName" : "num_1",
                                ... //剩下的暂时不重要
                        }
                }

可以看到,自动使用了新建立的索引,仅仅检查了4个文档(docsExamined),且用时为0ms,性能不知道高到哪里去

如果此时想强制使用_id索引,可以使用hint()函数:

> db.numbers.find({num:{"$gt":19995}}).hint({_id:1}).explain("executionStats")
                        ... //省略
                        "needTime" : 19996,
                        "needYield" : 0,
                        "saveState" : 156,
                        "restoreState" : 156,
                        "isEOF" : 1,
                        "invalidates" : 0,
                        "docsExamined" : 20000,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 20000,
                                "executionTimeMillisEstimate" : 10,
                                "works" : 20001,
                                "advanced" : 20000,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 156,
                                "restoreState" : 156,
                                "isEOF" : 1,
                                "invalidates" : 0,
                                "keyPattern" : {
                                        "_id" : 1
                                },
                                "indexName" : "_id_",
                        ... //省略

可以看到,此时索引变成了_id,且检查了20000个对象才完成查询

在数据发生变化(例如插入新数据)之后,索引会重新建立,也可以使用reIndex()函数显式地重建索引

MongoDB也支持哈希索引:

> db.user.createIndex({name:"hashed"})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 2,
        "numIndexesAfter" : 3,
        "ok" : 1
}

上面的命令是在name字段上建立了哈希索引。所谓哈希索引,就是先对字段的值取哈希,再根据哈希值进行索引,因此有以下限制:

  • 不支持范围查询
  • 不允许多键哈希索引
  • 浮点数会先转换成整数再取哈希,例如:4.2与4.3的哈希索引值是相同的

哈希索引的一大用处是进行数据分片,MongoDB默认使用id作为索引,但是其值相似性很高,分片时容易出现集聚,如果使用哈希索引,就能保证均衡。

MongoDB的索引是建立在内存中的,在我本机测试时,上述索引建立后,mongod进程使用的内存瞬间增加了12KB

和MySQL等数据库一样,MongoDB的索引的数据结构也是B树。在MongoDB中,每个结点分配了8KB空间(一般保持60%满),每个key最大1KB,不过每个key还有18B的额外空间开销,每个结点还有40B的额外开销,因此结点可能有一些未使用的空白空间。

2)覆盖索引查询

假如在该集合的查询中,显式将_id字段排除,那么此时就发生了覆盖索引查询:

  • 所有的查询字段是索引的一部分
  • 所有的查询返回字段在同一个索引中

覆盖查询直接从索引中查询数据,由于索引存放在内存中,查询起来会更快

3)索引失效

和其他数据库类似,某些情况下索引无法命中:

  • 查询条件包含正则表达式及非操作符,如 $nin, $not, 等。
  • 查询条件包含算术运算符,如 $mod, 等。
  • 查询条件包含$where 子句
> db.numbers.find({num:{$mod:[100,0]}},{num:1}).explain("executionStats")
... //省略
                                "docsExamined" : 200,
                                "alreadyHasObj" : 0,
                                "inputStage" : {
                                        "stage" : "IXSCAN",
                                        "filter" : {
                                                "num" : {
                                                        "$mod" : [
                                                                100,
                                                                0
                                                        ]
                                                }
                                        },
                                        "nReturned" : 200,
                                        "executionTimeMillisEstimate" : 10,
                                        "works" : 20001,
                                        "advanced" : 200,
                                        "needTime" : 19800,
                                        "needYield" : 0,
                                        "saveState" : 156,
                                        "restoreState" : 156,
                                        "isEOF" : 1,
                                        "invalidates" : 0,
                                        "keyPattern" : {
                                                "num" : 1
                                        },
                                        "indexName" : "num_1"
... //省略

可以看到,使用$mod操作符时,尽管使用了索引,但是无法命中,仍旧消耗了较长时间

4)索引限制

mongoDB的索引存放在内存里,因此有一定限制:

  • 索引键限制:索引项的总大小必须小于1024字节(可能包含了数据结构元数据信息的开销)
  • 单个集合的索引不能超过64个
  • 包含命名空间和点分隔符(即<database name>.<collection name>.$<index name>)的完全限定索引名不能长于128个字符。
  • 复合索引中的字段不能超过32个
  • 索引不能覆盖对数组字段的查询
  • 索引不能超过剩余内存空间大小,否则MongoDB会尝试清除已有的索引以释放空间,这会导致查询变慢

其他限制请参阅官方文档

2.全文索引

全文检索的原理是,对每一个词建立一个索引,指明该词在文章中出现的次数和位置,当用户查询时,检索程序就根据事先建立的索引进行查找,并返回查找的结果。这个过程类似于通过字典中的检索字表查字的过程。

1)索引建立

建立的方法和普通索引一样,只是把1换成"text",用以前建立的user集合来测试:

> db.user.createIndex({"hobby":"text"})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

如果使用的是2.6以前的版本,需要显式开启全文索引支持:

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

或者使用命令:mongod --setParameter textSearchEnabled=true

和普通索引一样,全文索引也可以在多个字段上建立。如果是想对所有字符串字段建立全文索引,可以使用:db.collection_name.createIndex({'$**':'text'})

这里有一个权重概念,即进行全文查询的时候,哪个字段上的索引更重要:

> db.user.createIndex({'$**':'text'},{weights:{name:10,sex:5,hobby:3}})
{
        "createdCollectionAutomatically" : false,
        "numIndexesBefore" : 1,
        "numIndexesAfter" : 2,
        "ok" : 1
}

权重越大越重要。

2)全文查询

然后就可以进行查询了:

> db.user.find({$text:{$search:'programming book'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming" }

要使用$text$search两个操作符

3)复杂全文查询

不过上述查询有个问题,我们查询的是programming book,但结果却是一个包含programming,一个包含book,即默认为or查询。如果指定必须包含在结果中的关键字,可以用双引号包裹住:

> db.user.find({$text:{$search:'programming "book"'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }

这次的查询结果只包含book了。如果对每个词都使用双引号,结果就相当于and查询:

> db.user.find({$text:{$search:'"programming" "book"'}})
>

可以看到,没有同时包含这两个词的文档,所以没有查询结果。

假如要排除指定关键词,可以用负号:

> db.user.find({$text:{$search:'programming -"book"'}})
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming" }

这次的查询结果变成了不包含book,且包含programming的文档。

全文查询有一个匹配度的概念,查看匹配度可以用$meta操作符:

> db.user.find({$text:{$search:'programming book'}},{score:{$meta:"textScore"}})
{ "_id" : ObjectId("5c3eef6d7da85af675c7c107"), "name" : "zhangsan", "sex" : "man", "age" : 21, "hobby" : "programming", "score" : 1 }
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book", "score" : 0.75 }

匹配度可以大于1。配合sort和limit,我们就可以筛选出最匹配的top-k个结果了。

4)多语言全文索引

全文索引建立时,可以手动指定索引的语言,默认英语

> db.user.createIndex({'$**':'text'},default_language:'french')

该语句就是建立了一个基于法语的全文索引:

> db.user.getIndexes()
[
        ……
                "name" : "$**_text",
                "ns" : "test.user",
                "weights" : {
                        "$**" : 1,
                        "hobby" : 3,
                        "name" : 10,
                        "sex" : 5
                },
                "default_language" : "french",
                "language_override" : "language",
                "textIndexVersion" : 3
        }
]

查询时,可以用$language操作符指定查询语言:

> db.user.find({$text:{$search:'book',$language:'french'}})
{ "_id" : ObjectId("5c3ef0037da85af675c7c109"), "name" : "wangwu", "sex" : "man", "age" : 18, "hobby" : "read book" }

需要注意一个停止词的概念,MongoDB中,如果遇到停止词,就不会对所在文档进行索引:

> db.user.find({$text:{$search:'de'}})
{ "_id" : ObjectId("5c5c2d13cc53c46e01541e01"), "name" : "de", "sex" : "man", "age" : 10, "hobby" : "none" }
> db.user.find({$text:{$search:'de',$language:'french'}})
>

可以看到,由于de是法语的停止词,导致没有查询结果

全文索引支持的语言有:danish丹麦语、dutch荷兰语、english英语、finnish芬兰语、french法语、german德语、hungarian匈牙利语、italian意大利语、norwegian挪威语、portuguese葡萄牙语、romanian罗马尼亚语、russian俄语、spanish西班牙语、swedish瑞典语、turkish土耳其语

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值