mongodb4.4索引

1.索引简介

数据库索引类似于图书索引。有了索引便不需要浏览整本书,而是可以采取一种快捷方式,只查看一个有内容引用的有序列表。这使得 MongoDB 的查找速度提高了好几个数量级。不使用索引的查询称为集合扫描,这意味着服务器端必须“浏览整本书”才能得到查询的结果。这个过程基本上类似于在一本没有索引的书中寻找信息:从第 1 页开始,通读整本书。通常来说,我们希望避免让服务器端执行集合扫描,因为对于大集合来说,该过程非常缓慢。首先创建一个100万个文档的集合

for (i=0; i<1000000; i++) {
  db.users2.insertOne(
  {
    "i" : i,
    "username" : "user"+i,
    "age" : Math.floor(Math.random()*120),
    "created" : new Date()
  }
  );
}

查看执行计划:

> db.users2.find({"username": "user101"}).explain("executionStats")
{
        "queryPlanner" : {
                "plannerVersion" : 1,
                "namespace" : "wang.users2",
                "indexFilterSet" : false,
                "parsedQuery" : {
                        "username" : {
                                "$eq" : "user101"
                        }
                },
                "winningPlan" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "user101"
                                }
                        },
                        "direction" : "forward"
                },
                "rejectedPlans" : [ ]
        },
        "executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1,                     #返回的结果数
                "executionTimeMillis" : 533,         #执行查询所用的毫秒数
                "totalKeysExamined" : 0,
                "totalDocsExamined" : 1000000,       #扫描的文档数
                "executionStages" : {
                        "stage" : "COLLSCAN",
                        "filter" : {
                                "username" : {
                                        "$eq" : "user101"
                                }
                        },
                        "nReturned" : 1,
                        "executionTimeMillisEstimate" : 48,
                        "works" : 1000002,
                        "advanced" : 1,
                        "needTime" : 1000000,
                        "needYield" : 0,
                        "saveState" : 1000,
                        "restoreState" : 1000,
                        "isEOF" : 1,
                        "direction" : "forward",
                        "docsExamined" : 1000000
                }
        },
        "serverInfo" : {
                "host" : "db10",
                "port" : 27017,
                "version" : "4.4.14",
                "gitVersion" : "0b0843af97c3ec9d2c0995152d96d2aad725aab7"
        },
        "ok" : 1
}
> 

(1).创建索引

现在尝试在 "username" 字段上创建一个索引。要创建索引,需要使用 createIndex 集合方法:

db.users2.createIndex({"username" : 1})

创建索引只需几秒的时间,除非集合特别大。如果 createIndex 调用在几秒后没有返回,则可以运行 db.currentOp()(在另一个 shell 中)或检查 mongod 的日志以查看索引创建的进度。

索引创建完成后,再次执行最初的查询:

executionStats" : {
                "executionSuccess" : true,
                "nReturned" : 1,
                "executionTimeMillis" : 1,
                "totalKeysExamined" : 1,
                "totalDocsExamined" : 1,
                "executionStages" : {
                        "stage" : "FETCH",
                        "nReturned" : 1,
                        "executionTimeMillisEstimate" : 0,
                        "works" : 2,
                        "advanced" : 1,
                        "needTime" : 0,
                        "needYield" : 0,
                        "saveState" : 0,
                        "restoreState" : 0,
                        "isEOF" : 1,
                        "docsExamined" : 1,
                        "alreadyHasObj" : 0,
                        "inputStage" : {
                                "stage" : "IXSCAN",
                                "nReturned" : 1,
                                "executionTimeMillisEstimate" : 0,
                                "works" : 2,
                                "advanced" : 1,
                                "needTime" : 0,
                                "needYield" : 0,
                                "saveState" : 0,
                                "restoreState" : 0,
                                "isEOF" : 1,
                                "keyPattern" : {
                                        "username" : 1
                                },
                                "indexName" : "username_1",
                                "isMultiKey" : false,
                                "multiKeyPaths" : {
                                        "username" : [ ]
                                },
                                "isUnique" : false,
                                "isSparse" : false,
                                "isPartial" : false,
                                "indexVersion" : 2,
                                "direction" : "forward",
                                "indexBounds" : {
                                        "username" : [
                                                "[\"user101\", \"user101\"]"
                                        ]
                                },
                                "keysExamined" : 1,
                                "seeks" : 1,
                                "dupsTested" : 0,
                                "dupsDropped" : 0
                        }
                }
        }

索引可以显著缩短查询时间。然而,使用索引是有代价的:修改索引字段的写操作(插入、更新和删除)会花费更长的时间。这是因为在更改数据时,除了更新文档,MongoDB还必须更新索引。通常来说,这个代价是值得的。关键是找出要索引的字段。

(2).复合索引简介

索引的目的是使查询尽可能高效。对于许多查询模式来说,在两个或更多的键上创建索引是必要的。例如,索引会将其所有值按顺序保存,因此按照索引键对文档进行排序的速度要快得多。然而,索引只有在作为排序的前缀时才有助于排序。例如,"username" 上的索引对下面这种排序就没什么帮助:

db.users2.find().sort({"age" : 1, "username" : 1})

这里是先根据 "age",然后再根据 "username" 进行排序的,所以严格按 "username" 排序并没有什么帮助。要优化这种排序,可以在 "age" 和 "username" 上创建索引:

db.users2.createIndex({"age" : 1, "username" : 1})

这称为复合索引(compound index)。如果查询中有多个排序方向或者查询条件中有多个键,那么这个索引会非常有用。复合索引是创建在多个字段上的索引。

假设有如下所示的一个 users2 集合,并且要执行带排序的查询:

(3).MongoDB如何选择索引

现在来看一下 MongoDB 是如何选择索引来满足查询的。假设有 5 个索引。当有查询进来时,MongoDB 会查看这个查询的形状。这个形状与要搜索的字段和一些附加信息(比如是否有排序)有关。基于这些信息,系统会识别出一组可能用于满足查询的候选索引。假设有一个查询进入,5 个索引中的 3 个被标识为该查询的候选索引。然后,MongoDB 会创建 3 个查询计划,分别为每个索引创建 1 个,并在 3 个并行线程中运行此查询,每个线程使用不同的索引。这样做的目的是看哪一个能够最快地返回结果。形象化地说,可以将其看作一场竞赛,如图 5-1 所示。这里的设计是,到达目标状态的第一个查询计划成为赢家。但更重要的是,以后会选择它作为索引,用于具有相同形状的其他查询。每个计划会相互竞争一段时间(称为试用期),之后每一次竞赛的结果都会用来在总体上计算出一个获胜的计划。

要赢得竞赛,查询线程必须首先返回所有查询结果或按排序顺序返回一些结果。考虑到在内存中执行排序的开销,其中排序的部分非常重要。

让多个查询计划相互竞争的真正价值在于,对于具有相同形状的后续查询,MongoDB 会知道要选择哪个索引。服务器端维护了查询计划的缓存。一个获胜的计划存储在缓存中,以备在将来用于进行该形状的查询。随着时间的推移以及集合和索引的变化,查询计划可能会从缓存中被淘汰。而 MongoDB 会再次进行尝试,以找到最适合当前集合和索引集的查询计划。其他会导致计划从缓存中被淘汰的事件有:重建特定的索引、添加或删除索引,或者显式清除计划缓存。此外,mongod 进程的重启也会导致查询计划缓存丢失。

在设计复合索引时需要注意:

1.等值过滤的键应该在最前面
2.用于排序的键应该在多值字段之前
3.多值过滤的键应该在最后面

(4).$运算符如何使用索引

有些查询可以比其他查询更高效地使用索引,有些查询则根本不能使用索引。

1).低效的运算符

通常来说,取反的效率是比较低的。 "$ne"  查询可以使用索引,但不是很有效。由于必须查看所有索引项,而不只是 "$ne"  指定的索引项,因此基本上必须扫描整个索引。

db.example.find({ "i": { "$ne": 3 } }).explain()
{
    "queryPlanner" : {
         ...,
        "parsedQuery" : {
            "i" : {
                "$ne" : "3"
            }
        },
        "winningPlan" : {
            {
                ...,
                "indexBounds" : {
                    "i" : [
                        [
                            {
                                "$minElement": 1
                            },
                            3
                        ],
                        [
                            3,
                            {
                                "$maxElement": 1
                            }
                        ]
                    ]
                }
            }
        },
        "rejectedPlans" : []
    },
    "serverInfo" : {
    ...,
    }

这个查询查找了所有小于 3 和大于 3 的索引项。如果索引中值为 3 的项非常多,那么这个查询的效率是比较高的,否则的话就必须检查大部分索引项了。

 "$not"  有时能够使用索引,但通常它并不知道要如何使用。它可以对基本的范围(比如将 {"key" : {"$lt" : 7}} 变为 {"key" : {"$gte" : 7}} )和正则表达式进行反转。然而,大多数使用 的查 "$not"  询会退化为全表扫描。而  "$nin"  总是使用全表扫描。

2).OR查询

MongoDB 在一次查询中仅能使用一个索引。也就是说,如果在 {"x" : 1} 上有一个索引,在 {"y" : 1} 上有另一个索引,然后在 {"x" : 123, "y" : 456} 上进行查询时,MongoDB 会使用其中一个索引,而不是两个一起使用。唯一的例外是  "$or" ,每个 "$or"  子句都可以使用一个索引,因为实际上  "$or"  是执行两次查询然后将结果集合并:

db.foo.find({ "$or": [{ "x": 123 }, { "y": 456 }] }).explain()
{
    "queryPlanner" : {
        "plannerVersion" : 1,
            "namespace" : "foo.foo",
                "indexFilterSet" : false,
                    "parsedQuery" : {
            "$or" : [
                {
                    "x": {
                        "$eq": 123
                    }
                },
                {
                    "y": {
                        "$eq": 456
                    }
                }
            ]
        },
        "winningPlan" : {
            "stage" : "SUBPLAN",
                "inputStage" : {
                "stage" : "FETCH",
                    "inputStage" : {
                    "stage" : "OR",
                        "inputStages" : [
                            {
                                "stage": "IXSCAN",
                                "keyPattern": {
                                    "x": 1
                                },
                                "indexName": "x_1",
                                "isMultiKey": false,
                                "multiKeyPaths": {
                                    "x": []
                                },
                                "isUnique": false,
                                "isSparse": false,
                                "isPartial": false,
                                "indexVersion": 2,
                                "direction": "forward",
                                "indexBounds": {
                                    "x": [
                                        "[123.0, 123.0]"
                                    ]
                                }
                            },
                            {
                                "stage": "IXSCAN",
                                "keyPattern": {
                                    "y": 1
                                },
                                "indexName": "y_1",
                                "isMultiKey": false,
                                "multiKeyPaths": {
                                    "y": []
                                },
                                "isUnique": false,
                                "isSparse": false,
                                "isPartial": false,
                                "indexVersion": 2,
                                "direction": "forward",
                                "indexBounds": {
                                    "y": [
                                        "[456.0, 456.0]"
                                    ]
                                }
                            }
                        ]
                }
            }
        },
        "rejectedPlans" : []
    },
    "serverInfo" : { 
     ...,
    },
    "ok" : 1
}

可以看到,这里的 explain 需要分别对两个索引进行两次单独的查询(由两个 "IXSCAN" 阶段表示)。通常来说,执行两次查询再将结果合并的效率不如单次查询高,因此应该尽可能使用  "$in" 

而不是  "$or" 。如果不得不使用 "$or",则要记住 MongoDB 需要检查两次查询的结果集并从中移除重复的文档(那些被多个 "$or" 子句匹配到的文档)。

除非使用排序,否则在用  "$in"  查询时无法控制返回文档的顺序。例如, {"x" : {"$in" : [1, 2, 3]}} 与 {"x" : {"$in" : [3, 2, 1]}}  返回的文档顺序是相同的。 

(5).索引对象和数组

MongoDB 允许深入文档内部,对内嵌字段和数组创建索引。内嵌对象和数组字段可以和顶级字段一起在复合索引中使用。尽管在某些方面比较特别,但是它们的大多数行为与“普通”索引字段是一致的。

1).索引内嵌文档

可以在内嵌文档的键上创建索引,方法与在普通键上创建索引相同。如果有一个集合,其中每个文档表示一个用户,那么可能会有一个描述每个用户位置的内嵌文档:

{
 "username" : "sid",
 "loc" : {
          "ip" : "1.2.3.4",
          "city" : "Springfield",
          "state" : "NY"
 }
}

可以在 "loc" 的其中一个子字段(如 "loc.city")上创建索引,以提高这个字段的查询速度:

db.users.createIndex({"loc.city" : 1})

可以用这种方式对任意深层次的字段(如 "x.y.z.w.a.b.c")创建索引。

注意,对内嵌文档本身(如 "loc")创建索引的行为与对内嵌文档的某个字段(如 "loc.city")创建索引的行为非常不同。对整个子文档创建索引只会提高针对整个子文档进行查询的速度。只有在进行与子文档字段顺序完全匹配的查询时(比如 db.users.find({"loc": {"ip" : "123.456.789.000", "city" : "Shelbyville", "state" : "NY"}}})),查询优化器才能使用 "loc" 上的索引。而对于 db.users.find({"loc.city" : "Shelbyville"}) 这样的查询是无法使用索引的。

2).索引数组

也可以对数组创建索引,这样就能高效地查找特定的数组元素了。

假设有一个博客文章集合,其中每个文档是一篇文章。每篇文章都有一个 "comments" 字段,这是一个由 "comment" 子文档组成的数组。如果想找出最近被评论次数最多的博客文章,可以在博客文章集合中内嵌的 "comments" 数组的 "date" 键上创建索引:

db.blog.createIndex({"comments.date" : 1})

对数组创建索引实际上就是对数组的每一个元素创建一个索引项,所以如果一篇文章有 20条评论,那么它就会有 20 个索引项。这使得数组索引的代价比单值索引要高:对于单次的插入、更新或删除,每一个数组项可能都需要更新(也许会有上千个索引项)。与前面 "loc" 的例子不同,整个数组是无法作为一个实体创建索引的:对数组创建索引就是对数组中的每个元素创建索引,而不是对数组本身创建索引。数组元素上的索引并不包含任何位置信息:要查找特定位置的数组元素(如 "comments.4"),查询是无法使用索引的。

顺便说一下,对某个特定的数组项进行索引是可以的,比如:

db.blog.createIndex({"comments.10.votes": 1})

然而,这个索引只有在精确匹配第 11 个数组元素的时候才会起作用(数组索引从 0 开始)。

索引项中只有一个字段是可以来自数组的。这是为了避免在多键索引中的索引项数量爆炸式地增长:每一对可能的元素都要被索引,这会导致每个文档都有 n*m 个索引项。假设有一个 {"x" : 1, "y" : 1} 上的索引:

> // x是一个数组——合法
> db.multi.insert({"x" : [1, 2, 3], "y" : 1})
>
> // y是一个数组——合法
> db.multi.insert({"x" : 1, "y" : [4, 5, 6]})
>
> // x和y都是数组——不合法!
> db.multi.insert({"x" : [1, 2, 3], "y" : [4, 5, 6]})
cannot index parallel arrays [y] [x]

假如 MongoDB 要为上面最后一个例子创建索引,那它就不得不创建如下这么多索引项(而这些数组只有 3 个元素):

{"x" : 1, "y" : 4}
{"x" : 1, "y" : 5}
{"x" : 1, "y" : 6}
{"x" : 2, "y" : 4}
{"x" : 2, "y" : 5}
{"x" : 2, "y" : 6}
{"x" : 3, "y" : 4}
{"x" : 3, "y" : 5} 
{"x" : 3, "y" : 6}

3).多键索引的影响

如果一个文档有被索引的数组字段,则该索引会立即被标记为多键索引。可以从 explain 的输出中看到一个索引是否为多键索引:如果使用了多键索引,则 "isMultikey" 字段的值会是true。

一旦一个索引被标记为多键,就再也无法变成非多键索引了,即使在该字段中包含数组的所有文档都被删除了也一样。恢复非多键索引的唯一方法是删除并重新创建这个索引。

多键索引可能会比非多键索引慢一些。可能会有许多索引项指向同一个文档,因此MongoDB 在返回结果之前可能需要做一些删除重复数据的操作。

2.explain输出

如你所见,explain 可以为查询提供大量的信息。对于慢查询来说,它是最重要的诊断工具之一。通过查看一个查询的 explain 输出,可以了解查询都使用了哪些索引以及是如何使用的。对于任何查询,都可以在末尾添加一个 explain 调用(就像添加 sort 或 limit 一样,但是 explain 必须是最后一个调用)。

最常见的 explain 输出有两种类型:使用索引的查询和未使用索引的查询。特殊类型的索引可能会创建略有不同的查询计划,但是大多数字段应该是相似的。此外,分片返回的是多个 explain 的集合,因为查询会在多个服务器端上执行。最基本的 explain 类型是不使用索引的查询。如果一个查询不使用索引,则是因为它使用了 "COLLSCAN"。

对于使用索引的查询,explain 的输出会有所不同,但在最简单的情况下,如果在 test.users上添加一个索引,那么它看起来会像下面这样:

test.users.find({ "age": 42 }).explain('executionStats')
{
    "queryPlanner" : {
        "plannerVersion" : 1,
            "namespace" : "test.users",
                "indexFilterSet" : false,
                    "parsedQuery" : {
            "age" : {
                "$eq" : 42
            }
        },
        "winningPlan" : {
            "stage" : "FETCH",
                "inputStage" : {
                "stage" : "IXSCAN",
                    "keyPattern" : {
                    "age" : 1,
                        "username" : 1
                },
                "indexName" : "age_1_username_1",
                    "isMultiKey" : false,
                        "multiKeyPaths" : {
                    "age" : [],
                        "username" : []
                },
                "isUnique" : false,
                    "isSparse" : false,
                        "isPartial" : false,
                            "indexVersion" : 2,
                                "direction" : "forward",
                                    "indexBounds" : {
                    "age" : [
                        "[42.0, 42.0]"
                    ],
                        "username" : [
                            "[MinKey, MaxKey]"
                        ]
                }
            }
        },
        "rejectedPlans" : []
    },
    "executionStats" : {
        "executionSuccess" : true,
            "nReturned" : 8449,
                "executionTimeMillis" : 15,
                    "totalKeysExamined" : 8449,
                        "totalDocsExamined" : 8449,
                            "executionStages" : {
            "stage" : "FETCH",
                "nReturned" : 8449,
                    "executionTimeMillisEstimate" : 10,
                        "works" : 8450,
                            "advanced" : 8449,
                                "needTime" : 0,
                                    "needYield" : 0,
                                        "saveState" : 66,
                                            "restoreState" : 66,
                                                "isEOF" : 1,
                                                    "invalidates" : 0,
                                                        "docsExamined" : 8449,
                                                            "alreadyHasObj" : 0,
                                                                "inputStage" : {
                "stage" : "IXSCAN",
                    "nReturned" : 8449,
                        "executionTimeMillisEstimate" : 0,
                            "works" : 8450,
                                "advanced" : 8449,
                                    "needTime" : 0,
                                        "needYield" : 0,
                                            "saveState" : 66,
                                                "restoreState" : 66,
                                                    "isEOF" : 1,
                                                        "invalidates" : 0,
                                                            "keyPattern" : {
                    "age" : 1,
                        "username" : 1
                },
                "indexName" : "age_1_username_1",
                    "isMultiKey" : false,
                        "multiKeyPaths" : {
                    "age" : [],
                        "username" : []
                },
                "isUnique" : false,
                    "isSparse" : false,
                        "isPartial" : false,
                            "indexVersion" : 2,
                                "direction" : "forward",
                                    "indexBounds" : {
                    "age" : [
                        "[42.0, 42.0]"
                    ],
                        "username" : [
                            "[MinKey, MaxKey]"
                        ]
                },
                "keysExamined" : 8449,
                    "seeks" : 1,
                        "dupsTested" : 0,
                            "dupsDropped" : 0,
                                "seenInvalidated" : 0
            }
        }
    },
    "serverInfo" : {
        "host" : "eoinbrazil-laptop-osx",
            "port" : 27017,
                "version" : "4.0.12",
                    "gitVersion" : "5776e3cbf9e7afe86e6b29e22520ffb6766e95d4"
    },
    "ok" : 1
}

这个输出会首先告诉你使用了哪个索引:test.users。接下来是实际返回了多少文档:"nReturned"。注意,这并不一定能反映出 MongoDB 在执行查询时做了多少工作(例如,它需要搜索多少索引和文档)。"totalKeysExamined" 描述了所扫描的索引项数量,"totalDocsExamined" 表示扫描了多少个文档。

此输出还显示了没有 rejectedPlans,并且对值为 42.0 的索引使用了有界搜索。"executionTimeMillis" 报告了查询的执行速度,即从服务器接收请求到发出响应的时间。然而,这可能并不总是你希望看到的值。如果 MongoDB 尝试了多个查询计划,那么"executionTimeMillis" 反映的是所有查询计划花费的总运行时间,而不是所选的最优查询计划所花费的时间。

现在你已经了解了这些基础知识,下面是对一些重要字段的详细介绍:

"isMultiKey" : false
     本次查询是否使用了多键索引。
"nReturned" : 8449
     本次查询返回的文档数量。
"totalDocsExamined" : 8449
     MongoDB 按照索引指针在磁盘上查找实际文档的次数。如果查询中包含的查询条件不是索引的一部分,
     或者请求的字段没有包含在索引中,MongoDB 就必须查找每个索引项所指向的文档。     
"totalKeysExamined" : 8449
     如果使用了索引,那么这个数字就是查找过的索引条目数量。如果本次查询是一次全表
     扫描,那么这个数字就表示检查过的文档数量。
"stage" : "IXSCAN"
     MongoDB 是否可以使用索引完成本次查询。如果不可以,那么会使用 "COLLSCAN" 表示必须执行集合扫描来完成查询。
     在本例中,可以看出 MongoDB 使用索引找到了所有匹配的文档,因为 "totalKeysExamined""totalDocsExamined" 是一样的。不过,此查询需要返回匹配文档中的每个字段,而索引中只包含了 "age" 字段和 "username" 字段。
"needYield" : 0
     为了让写请求顺利进行,本次查询所让步(暂停)的次数。如果有写操作在等待执行,那么查询将定期释放它们的锁以允许写操作执行。
     在本次查询中,由于并没有写操作在等待,因此查询永远不会进行让步。
"executionTimeMillis" : 15
     数据库执行本次查询所花费的毫秒数。这个数字越小越好。
"indexBounds" : {...}
     这描述了索引是如何被使用的,并给出了索引的遍历范围。在本例中,由于查询中的第一个子句是精确匹配,因此索引只需要查找 42 这个值就可以了。
     第二个索引键是一个自由变量,因为查询没有对它进行任何限制。因此,数据库会在符合 "age" : 42 的结果中查找用户名在负无穷
     ("$minElement" : 1)和正无穷("$maxElement" : 1)之间的数据。

3.索引类型

(1).唯一索引

唯一索引确保每个值最多只会在索引中出现一次。如果想保证不同文档的 "firstname" 键拥有不同的值,则可以使用 partialFilterExpression 仅为那些有 firstname 字段的文档创建唯一索引:

db.users.createIndex({"firstname" : 1},{"unique" : true} )

假如试图向 users 集合中插入以下文档:

db.users.insert({firstname: "bob"})

(2).部分索引

正如上面提到的,唯一索引会将 null 作为值,因此无法在多个文档缺少键的情况下使用唯一索引。然而,在很多情况下,你可能希望仅在键存在时才强制执行唯一索引。如果一个字段可能存在也可能不存在,但当其存在时必须是唯一的,那么可以将 "unique" 选项与"partial" 选项组合在一起使用。

要创建部分索引,需要包含 "partialFilterExpression" 选项。部分索引提供了稀疏索引功能的超集,使用一个文档来表示希望在其上创建索引的过滤器表达式。如果有一个电子邮件地址字段是可选的,但是如果提供了这个字段,那么它的值就必须是唯一的。我们可以这样做:

db.users.createIndex({"email" : 1}, {"unique" : true, "partialFilterExpression" :{ email: { $exists: true } }})

部分索引不必是唯一的。要创建非唯一的部分索引,只需去掉 "unique" 选项即可。需要注意的一点是,根据是否使用部分索引,相同的查询可能返回不同的结果。假设有一个集合,其中大多数文档有 "x" 字段,但有一个文档没有:

db.foo.insertMany([
 { "_id" : 0 },
 { "_id" : 1, "x" : 1 },
 { "_id" : 2, "x" : 2 },
 { "_id" : 3, "x" : 3 }
])

当在 "x" 上执行查询时,它会返回所有匹配的文档:

db.foo.find({"x" : {"$ne" : 2}})

4.索引管理

如前文所述,可以使用 createIndex 函数创建新的索引。每个集合只需要创建一次索引。如果再次尝试创建相同的索引,则不会执行任何操作。关于数据库索引的所有信息都存储在 system.indexes 集合中。这是一个保留集合,因此不能修改其中的文档或从中删除文档。只能通过 createIndex、createIndexes 和 dropIndexes数据库命令来对它进行操作。

创建一个索引后,可以在 system.indexes 中看到它的元信息。也可以执行 db.collectionName.getIndexes()来查看给定集合中所有索引的信息

db.students.getIndexes()
[
    {
        "v": 2,
        "key": {
            "_id": 1
        },
        "name": "_id_",
        "ns": "school.students"
    },
    {
        "v": 2,
        "key": {
            "class_id": 1
        },
        "name": "class_id_1",
        "ns": "school.students"
    },
    {
        "v": 2,
        "key": {
            "student_id": 1,
            "class_id": 1
        },
        "name": "student_id_1_class_id_1",
        "ns": "school.students"
    }
]

其中重要的字段是 "key" 和 "name"。此处的键可用于 hint 及其他必须指定索引的地方。

db.students.find({"student_id" : 'zsn', "class_id" : 'aa01'}).hint({"student_id" : 'zsn', "class_id" : 'aa01'})

这里字段的顺序很重要:{"class_id" : 1, "student_id" : 1} 上的索引与 {"student_id" : 1, "class_id" : 1} 上的索引并不相同。索引名称被用作许多管理索引操作的标识符,比如 dropIndexes。而索引是否为多键没有在此规范中指定。"v" 字段在内部用于索引的版本控制。如果有任何索引不包含 "v" : 1 这样的字段,那么说明这个索引是以一种效率较低的旧方式存储的。确保至少运行 MongoDB 2.0,并删除和重建索引,就可以对其进行升级了。

(1).标识索引

集合中的每个索引都有一个可用于标识该索引的名称,服务器端用这个名称来对其进行删除或者操作。索引名称的默认形式是 keyname1_dir1_keyname2_dir2_..._keynameN_dirN,其中 keynameX 是索引的键,dirX 是索引的方向(1 或 -1)。如果索引包含两个以上的键,那么这种方式就会很麻烦,因此可以将自己的名称指定为 createIndex 的选项之一:

db.soup.createIndex({"a" : 1, "b" : 1, "c" : 1, ..., "z" : 1},{"name" : "alphabet"})

索引名称是有字符数限制的,因此在创建复杂的索引时可能需要自定义名称。调用getLastError 就可以知道索引是否创建成功,或者为什么创建失败。

(2).修改索引

随着应用程序不断变化,你可能会发现数据或者查询已经发生了改变,原先的索引也不那么好用了。可以使用 dropIndex 命令删除不再需要的索引:

> db.people.dropIndex("x_1_y_1")
{ "nIndexesWas" : 3, "ok" : 1 }

使用索引描述中的 "name" 字段来指定要删除的索引。

创建新的索引既费时又耗费资源。在 4.2 版本之前,MongoDB 会尽可能快地创建索引,阻塞数据库上的所有读写操作,直到索引创建完成。如果希望数据库对读写保持一定的响应,那么可以在创建索引时使用 "background" 选项。这会迫使索引创建不时地让步于其他操作,但仍可能对应用程序的性能造成严重影响。后台创建索引也会比前台创建索引慢得多。MongoDB 4.2 引入了一种新的方式,即混合索引创建。它只在索引创建的开始和结束时持有排他锁。创建过程的其余部分会交错地让步于读写操作。在 MongoDB 4.2 中,这种方式同时替换了前台和后台类型的索引创建。如果可以选择,在现有文档中创建索引要比先创建索引然后插入所有文档中稍微快一些。

官方地址:https://www.mongodb.com/docs/upcoming/indexes/

posted @ 2022-08-14 16:56  雍洲无名  阅读(207)  评论(0编辑  收藏  举报