在适用于 MongoDB 的 Azure Cosmos DB 中管理索引编制

适用对象: MongoDB

Azure Cosmos DB for MongoDB 利用 Azure Cosmos DB 的核心索引管理功能。 本文重点介绍了如何使用 Azure Cosmos DB for MongoDB 添加索引。 索引是一种专用的数据结构,可使数据的查询速度大概提升一个量级。

适用于 MongoDB 服务器 3.6 及更高版本的索引编制功能

Azure Cosmos DB for MongoDB 服务器版本 3.6+ 会自动为 _id 字段和分片键(仅在分片集合中)编制索引。 API 会自动强制确保每个分片密钥的 _id 字段的唯一性。

API for MongoDB 的行为不同于 Azure Cosmos DB for NoSQL,后者默认情况下会对所有字段编制索引。

编辑索引策略

建议在 Azure 门户的数据资源管理器中编辑索引策略。 可以在数据资源管理器中的索引策略编辑器中添加单个字段和通配符索引:

Indexing policy editor

注意

无法使用数据资源管理器中的索引策略编辑器来创建复合索引。

索引类型

单个字段

只能对任何单个字段创建索引。 单字段索引的排序顺序并不重要。 以下命令对字段 name 创建索引:

db.coll.createIndex({name:1})

可以在 Azure 门户的 name 上创建相同的单个字段索引:

Add name index in indexing policy editor

在适用的情况下,一个查询将使用多个单字段索引。 对于每个集合,最多可以创建 500 个单字段索引。

复合索引(MongoDB 服务器版本 3.6+)

在 API for MongoDB 中,如果查询需要能够同时对多个字段排序,则需要复合索引。 对于包含多个不需要排序的筛选器的查询,请创建多个单字段索引而不是一个复合索引,以便节省索引编制成本。

复合索引或复合索引中每个字段的单字段索引会在查询中提供相同的筛选性能。

由于数组的局限性,默认情况下不支持嵌套字段的复合索引。 如果嵌套字段不包含数组,索引将按正常工作。 如果嵌套字段包含数组(路径中任意位置),索引将忽略该值。

例如,包含 people.dylan.age 的复合索引在以下情况下将正常工作,因为路径中没有数组:

{
  "people": {
    "dylan": {
      "name": "Dylan",
      "age": "25"
    },
    "reed": {
      "name": "Reed",
      "age": "30"
    }
  }
}

相同的复合索引在以下情况下会失效,因为路径中有一个数组:

{
  "people": [
    {
      "name": "Dylan",
      "age": "25"
    },
    {
      "name": "Reed",
      "age": "30"
    }
  ]
}

可通过启用“EnableUniqueCompoundNestedDocs”功能为数据库帐户启用此功能。

注意

不能基于数组创建复合索引。

以下命令对字段 nameage 创建复合索引:

db.coll.createIndex({name:1,age:1})

可以使用复合索引来同时对多个字段进行高效排序,如以下示例中所示:

db.coll.find().sort({name:1,age:1})

还可以使用前面的复合索引,在一个查询中对所有字段按降序进行高效排序。 下面是一个示例:

db.coll.find().sort({name:-1,age:-1})

但是,复合索引中的路径顺序必须与查询完全匹配。 下面是一个需要其他复合索引的查询示例:

db.coll.find().sort({age:1,name:1})

多键索引

Azure Cosmos DB 创建多键索引来为数组中存储的内容编制索引。 如果为带有数组值的字段编制索引,则 Azure Cosmos DB 会自动为数组中的每个元素编制索引。

空间索引

许多地理空间运算符可受益于地理空间索引。 Azure Cosmos DB for MongoDB 目前支持 2dsphere 索引。 该 API 尚不支持 2d 索引。

下面是对 location 字段创建地理空间索引的示例:

db.coll.createIndex({ location : "2dsphere" })

文本索引

Azure Cosmos DB for MongoDB 目前支持文本索引。 要对字符串运行文本搜索查询,应使用 Azure AI 搜索与 Azure Cosmos DB 的集成。

通配符索引

可以使用通配符索引来支持针对未知字段的查询。 假设你有一个包含有关家庭的数据的集合。

以下是该集合中的示例文档的一部分:

"children": [
   {
     "firstName": "Henriette Thaulow",
     "grade": "5"
   }
]

以下是另一个示例,这次在 children 中有一组稍微不同的属性:

"children": [
    {
     "familyName": "Merriam",
     "givenName": "Jesse",
     "pets": [
         { "givenName": "Goofy" },
         { "givenName": "Shadow" }
         ]
   },
   {
     "familyName": "Merriam",
     "givenName": "John",
   }
]

在此集合中,文档可以拥有许多不同的可能属性。 如果要为 children 数组中的所有数据编制索引,则有两个选择:为每个单独的属性创建单独的索引,或为整个 children 数组创建一个通配符索引。

创建通配符索引

以下命令在 children 内的任何属性上创建通配符索引:

db.coll.createIndex({"children.$**" : 1})

与在 MongoDB 中不同,通配符索引可以在查询谓词中支持多个字段。 如果使用一个通配符索引,而不是为每个属性创建单独的索引,查询性能不会有差异。

可以使用通配符语法创建以下索引类型:

  • 单个字段
  • 地理空间

为所有属性编制索引

在所有字段上创建通配符索引的方法如下:

db.coll.createIndex( { "$**" : 1 } )

还可以使用 Azure 门户中的数据资源管理器创建通配符索引:

Add wildcard index in indexing policy editor

注意

如果你刚开始开发,强烈建议你在所有字段上使用通配符索引。 这可以简化开发,并使优化查询更加容易。

具有多个字段的文档可能会在写入和更新时产生较高的请求单位 (RU) 费用。 因此,如果有写入密集型工作负荷,则应选择单独的索引路径,而不要使用通配符索引。

注意

在包含数据的现有集合上,对唯一索引的支持现提供预览版。 可通过启用“EnableUniqueIndexReIndex”功能为数据库帐户启用此功能。

限制

通配符索引不支持以下任何索引类型或属性:

  • 复合
  • TTL
  • 唯一

与在 MongoDB 中不同,在 Azure Cosmos DB for MongoDB 中,不能使用通配符索引进行以下操作:

  • 创建包含多个特定字段的通配符索引

    db.coll.createIndex(
        { "$**" : 1 },
        { "wildcardProjection " :
            {
               "children.givenName" : 1,
               "children.grade" : 1
            }
        }
    )
    
  • 创建排除多个特定字段的通配符索引

    db.coll.createIndex(
        { "$**" : 1 },
        { "wildcardProjection" :
            {
               "children.givenName" : 0,
               "children.grade" : 0
            }
        }
    )
    

作为替代方法,你可以创建多个通配符索引。

索引属性

对于处理线路协议版本 4.0 的帐户和处理更早版本的帐户,以下操作常用。 还可以详细了解支持的索引和已编制索引的属性

唯一索引

对于编制了索引的字段,唯一索引用于确保这些字段的同一值不会存在于两个或两个以上的文档中。

以下命令对字段 student_id 创建唯一索引:

globaldb:PRIMARY> db.coll.createIndex( { "student_id" : 1 }, {unique:true} )
{
    "_t" : "CreateIndexesResponse",
    "ok" : 1,
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 1,
    "numIndexesAfter" : 4
}

对于分片的集合,必须提供分片(分区)键才能创建唯一索引。 换言之,在分片集合上的所有唯一索引都是复合索引,其中的一个字段是分片键。 顺序中的第一个字段应为分片键。

以下命令创建一个分片的集合 coll(分片键为 university),该集合具有 student_iduniversity 字段上的唯一索引:

globaldb:PRIMARY> db.runCommand({shardCollection: db.coll._fullName, key: { university: "hashed"}});
{
    "_t" : "ShardCollectionResponse",
    "ok" : 1,
    "collectionsharded" : "test.coll"
}
globaldb:PRIMARY> db.coll.createIndex( { "university" : 1, "student_id" : 1 }, {unique:true});
{
    "_t" : "CreateIndexesResponse",
    "ok" : 1,
    "createdCollectionAutomatically" : false,
    "numIndexesBefore" : 3,
    "numIndexesAfter" : 4
}

在前面的示例中,省略 "university":1 子句将返回包含以下消息的错误:

cannot create unique index over {student_id : 1.0} with shard key pattern { university : 1.0 }

限制

在集合为空时,需要创建唯一索引。

由于数组的局限性,默认情况下不支持嵌套字段上的唯一索引。 如果嵌套字段不包含数组,索引将按预期正常工作。 如果嵌套字段包含数组(路径中的任意位置),唯一索引将忽略该值,并且不会为该值保留唯一性。

例如,people.tom.age 的唯一索引在以下情况下将正常工作,因为路径中没有数组:

{ "people": { "tom": { "age": "25" }, "mark": { "age": "30" } } }

但在以下情况下不会正常工作,因为路径中有数组:

{ "people": { "tom": [ { "age": "25" } ], "mark": [ { "age": "30" } ] } }

可通过启用“EnableUniqueCompoundNestedDocs”功能为数据库帐户启用此功能。

TTL 索引

若要在特定集合中启用文档过期,需创建生存时间 (TTL) 索引。 TTL 索引是具有 expireAfterSeconds 值的 _ts 字段上的索引。

示例:

globaldb:PRIMARY> db.coll.createIndex({"_ts":1}, {expireAfterSeconds: 10})

上面的命令会在 db.coll 集合中删除过去 10 秒内未修改的任何文档。

注意

_ts 字段是特定于 Azure Cosmos DB 的字段,不可从 MongoDB 客户端访问。 它是一个保留(系统)属性,其中包含文档上次进行修改时的时间戳。

跟踪索引进度

Azure Cosmos DB for MongoDB 版本 3.6+ 支持使用 currentOp() 命令来跟踪数据库实例上的索引进度。 此命令返回一个文档,其中包含有关数据库实例上正在进行的操作的信息。 可使用 currentOp 命令跟踪本机 MongoDB 中所有正在进行的操作。 在 Azure Cosmos DB for MongoDB 中,此命令仅支持跟踪索引操作。

下面这些示例演示如何使用 currentOp 命令来跟踪索引进度:

  • 获取集合的索引进度:

    db.currentOp({"command.createIndexes": <collectionName>, "command.$db": <databaseName>})
    
  • 获取数据库中所有集合的索引进度:

    db.currentOp({"command.$db": <databaseName>})
    
  • 获取 Azure Cosmos DB 帐户中所有数据库和集合的索引进度:

    db.currentOp({"command.createIndexes": { $exists : true } })
    

索引进度输出示例

索引进度详细信息显示当前索引操作的进度百分比。 以下示例显示索引进度的不同阶段的输出文档格式:

  • 如果针对“foo”集合与“bar”数据库执行了索引操作,且该操作已完成 60%,那么该操作将有以下输出文档。 Inprog[0].progress.total 字段将 100 显示为目标完成百分比。

    {
          "inprog" : [
          {
                  ………………...
                  "command" : {
                          "createIndexes" : foo
                          "indexes" :[ ],
                          "$db" : bar
                  },
                  "msg" : "Index Build (background) Index Build (background): 60 %",
                  "progress" : {
                          "done" : 60,
                          "total" : 100
                  },
                  …………..…..
          }
          ],
          "ok" : 1
    }
    
  • 如果索引操作刚刚针对“foo”集合与“bar”数据库启动,那么在进度达到可度量的级别之前,输出文档可能会一直显示 0% 进度。

    {
          "inprog" : [
          {
                  ………………...
                  "command" : {
                          "createIndexes" : foo
                          "indexes" :[ ],
                          "$db" : bar
                  },
                  "msg" : "Index Build (background) Index Build (background): 0 %",
                  "progress" : {
                          "done" : 0,
                          "total" : 100
                  },
                  …………..…..
          }
          ],
         "ok" : 1
    }
    
  • 正在进行的索引操作完成后,输出文档将显示 inprog 操作为空。

    {
        "inprog" : [],
        "ok" : 1
    }
    

后台索引更新

无论为 Background 索引属性指定了什么值,索引更新始终会在后台完成。 由于索引更新操作使用请求单位 (RU) 的优先级低于其他数据库操作,因此,索引更改不会导致写入、更新或删除操作无法正常进行。

添加新索引时,对读取可用性没有影响。 索引转换完成之后,查询将只利用新索引。 在索引转换过程中,查询引擎将继续使用现有的索引,因此,在索引转换过程中,你将观察到,读取性能类似于在启动索引更改之前观察到的情况。 添加新索引时,也不会有查询结果不完整或不一致的风险。

删除索引并立即运行在删除的索引上有筛选器的查询时,在索引转换完成之前,结果可能不一致且不完整。 如果删除索引,则当查询对这些新删除的索引进行筛选时,查询引擎将无法提供一致或完整的结果。 大多数开发人员不会在删除索引后立即尝试查询它们,因此,在实践中,这种情况不太可能发生。

注意

可以跟踪索引进度

ReIndex 命令

reIndex 命令会重新创建集合上的所有索引。 在极少数情况下,可以运行 reIndex 命令来解决集合中的查询性能或其他索引问题。 如果在编制索引时遇到问题,建议的方法是使用 reIndex 命令重新创建索引。

可以使用以下语法运行 reIndex 命令:

db.runCommand({ reIndex: <collection> })

可使用以下语法来检查运行 reIndex 命令能否提高集合中的查询性能:

db.runCommand({"customAction":"GetCollection",collection:<collection>, showIndexes:true})

示例输出:

{
        "database" : "myDB",
        "collection" : "myCollection",
        "provisionedThroughput" : 400,
        "indexes" : [
                {
                        "v" : 1,
                        "key" : {
                                "_id" : 1
                        },
                        "name" : "_id_",
                        "ns" : "myDB.myCollection",
                        "requiresReIndex" : true
                },
                {
                        "v" : 1,
                        "key" : {
                                "b.$**" : 1
                        },
                        "name" : "b.$**_1",
                        "ns" : "myDB.myCollection",
                        "requiresReIndex" : true
                }
        ],
        "ok" : 1
}

如果 reIndex 可提高查询性能,则 requiresReIndex 为 true。 如果 reIndex 不能提高查询性能,则省略此属性。

迁移带索引的集合

目前,仅当集合不包含文档时才能创建唯一索引。 常用 MongoDB 迁移工具会尝试在导入数据后创建唯一索引。 若要规避此问题,可以手动创建相应的集合和唯一索引,而不是允许迁移工具尝试。 (可以在命令行中使用 --noIndexRestore 标志来为 mongorestore 实现此行为。)

适用于 MongoDB 版本 3.2 的索引编制功能

对于与 MongoDB Wire Protocol 版本 3.2 兼容的 Azure Cosmos DB 帐户,可用的索引编制功能和默认值是不同的。 可以检查帐户的版本升级到版本 3.6

如果使用的是版本 3.2,请阅读此部分,其中概述了版本 3.2 与版本 3.6+ 之间的重要差别。

删除默认索引(版本 3.2)

与 Azure Cosmos DB for MongoDB 版本 3.6+ 不同,版本 3.2 默认会为每个属性编制索引。 可以使用以下命令删除集合 (coll) 的这些默认索引:

> db.coll.dropIndexes()
{ "_t" : "DropIndexesResponse", "ok" : 1, "nIndexesWas" : 3 }

删除默认索引后,可以像在版本 3.6+ 中那样添加更多索引。

复合索引(版本 3.2)

复合索引包含对文档多个字段的引用。 若要创建复合索引,请升级到版本 3.6 或 4.0

通配符索引(版本 3.2)

若要创建通配符索引,请升级到版本 4.0 或 3.6

后续步骤