MongoDB——多键索引

1,089 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情

由于MongoDB灵活的数据模型,我们能够将数组嵌入到我们的文档中。当我们给一个数组创建索引时,就是多键索引。之所以叫多键索引,是因为MongoDB会给数组中的每一个元素都创建一个索引键。这些多键索引支持对数组字段的高效查询。

多键索引可以在字符串、数字和内嵌文档的数组上创建。

创建多键索引

db.<collection>.createIndex( { <field>: < 1 or -1 > } )

MongoDB 3.4 开始,对于3.4及更高版本创建多键索引,会跟踪那些字段导致索引成为多键索引。因此,我们不需要明确指定多键类型。

使用

插入以下下数据

use test
db.products.insert({
  productName: "MongoDB Short Sleeve T-Shirt",
  categories: ["T-Shirts", "Clothing", "Apparel"],
  stock: { size: "L", color: "green", quantity: 100 }
});

例如在上面这个文档中,我们要为categories创建索引,那么服务器将为T-ShirtClothingApparel创建3个索引键,每个子文档一个。

对于每个索引文档,我们最多可以有一个索引字段,其值为数组。在上面的文档中,我们不能同时为categoriesstock.quantity 创建索引。假如要创建复合索引,那么将为他们创建categoriesstock.quantity数组长度之间的笛卡尔积,会产生大量的索引键,对查询性能影响很大。

创建多键索引要注意,因为我们要确保我们的数组不会变的太大。因为这样做会影响索引的大小,可能会导致将索引加载到内存中,将数据查询出来的问题。多键索引不支持覆盖查询。

我们给stock.quantity创建索引,然后在查询一下

db.products.createIndex({ "stock.quantity": 1})
var exp = db.products.explain()
exp.find({ "stock.quantity": 100 })
{
  queryPlanner: {
    namespace: 'test.products',
    indexFilterSet: false,
    parsedQuery: { 'stock.quantity': { '$eq': 100 } },
    queryHash: 'AFCEECBB',
    planCacheKey: '4A0F65D8',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      inputStage: {
        stage: 'IXSCAN',
        keyPattern: { 'stock.quantity': 1 },
        indexName: 'stock.quantity_1',
        //是否使用多键索引
        isMultiKey: false,
        multiKeyPaths: { 'stock.quantity': [] },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: { 'stock.quantity': [ '[100, 100]' ] }
      }
    },
    rejectedPlans: []
  }
}

我们可以看到使用了索引扫描,但是没有使用成功(isMultiKey = false),多键索引时假的,这是有道理的,因为我们没有在数组字段上创建索引,再插入一条记录,stock 为内嵌数组。

db.products.insert({
  productName: "MongoDB Long Sleeve T-Shirt",
  categories: ["T-Shirts", "Clothing", "Apparel"],
  stock: [
    { size: "S", color: "red", quantity: 25 },
    { size: "S", color: "blue", quantity: 10 },
    { size: "M", color: "blue", quantity: 50 }
  ]
});
exp.find({ "stock.quantity": 100 })
{
  queryPlanner: {
    namespace: 'test.products',
    indexFilterSet: false,
    parsedQuery: { 'stock.quantity': { '$eq': 100 } },
    queryHash: 'AFCEECBB',
    planCacheKey: '4A0F65D8',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      inputStage: {
        stage: 'IXSCAN',
        keyPattern: { 'stock.quantity': 1 },
        indexName: 'stock.quantity_1',
        isMultiKey: true,
        multiKeyPaths: { 'stock.quantity': [ 'stock' ] },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: { 'stock.quantity': [ '[100, 100]' ] }
      }
    },
    rejectedPlans: []
  }
}

现在我们可以看到依然是索引扫描,但是使用了多键索引(isMultiKey = true),因为MongoDB将文档插入到该索引字段位于数组中的位置时才识别索引是多键的。

如果我们尝试创建两个字段都是数组的索引

db.products.createIndex({ "stock.quantity": 1, "categories": 1})
MongoServerError: Index build failed: 8cc81f7e-30fb-41f8-aa6a-f11c5c979e80: Collection test.products ( bde69fd0-a25b-418f-9da1-7af93c63442b ) :: caused by :: cannot index parallel arrays [categories] [stock]

但是我们仍然可以给stock.quantityproductName创建复合多键索引。也可以为stock.quantitystock.size 创建索引,因为他们是同一个数组中的两个字段。

db.products.createIndex({ "stock.quantity": 1, "productName": `})
stock.quantity_1_productName_1
db.products.createIndex({ "stock.quantity": 1, "stock.size": 1})
stock.quantity_1_stock.size_1