首页 > 数据库 >MongoDB 通配符索引 (wildcard index) 的利与弊

MongoDB 通配符索引 (wildcard index) 的利与弊

时间:2023-12-27 20:22:24浏览次数:45  
标签:index name 利与弊 通配符 db 索引 user userMetadata wildcard

MongoDB 支持在单个字段或多个字段上创建索引,以提高查询性能。MongoDB 支持灵活的模式,这意味着文档字段名在集合中可能会有所不同。使用通配符索引可支持针对任意或未知字段的查询。

·一个集合中可以创建多个通配符索引

·通配符索引可以覆盖与集合中其他索引相同的字段

·通配符索引默认省略 _id 字段。要在通配符索引中包含 _id 字段,必须明确指定 { "_id" : 1 }

·通配符索引是稀疏索引,只包含具有索引字段的文档条目,即使索引字段包含空值也是如此

·通配符索引 (wildcard index) 与通配符文本索引 (wildcard text index) 不同,也不兼容。通配符索引不支持使用 $text 操作符的查询。

 

要创建通配符索引,使用通配符指定符 ($**) 作为索引键:

db.collection.createIndex( { "$**": <sortOrder> } )

 

通配符索引的简单理念是,在不预先知道文档中的字段的情况下,提供创建索引的可能性。你可以输入任何你需要的内容,MongoDB 会索引所有内容,无论字段名称如何,无论数据类型如何。这项功能看起来很神奇,但也付出了一些代价。

 

为了测试通配符索引,让我们创建一个用于存储用户详细信息的小型集合。集合有一些固定字段,如姓名、出生日期和性别,但也有一个子文档 userMetadata,用于存储我们事先不知道的任何其他属性。这样,我们就可以存储所需的一切。

 

插入数据

db.user.insert( { name: "John", date_of_birth: new ISODate("2001-02-05"), gender: 'M', userMetadata: { "likes" : [ "dogs", "cats" ] } } )
db.user.insert( { name: "Marie", date_of_birth: new ISODate("2008-03-12"), gender: 'F', userMetadata: { "dislikes" : "hamsters" } } )
db.user.insert( { name: "Tom", date_of_birth: new ISODate("1998-12-23"), gender: 'M', userMetadata: { "age" : 25 } } )
db.user.insert( { name: "Adrian", date_of_birth: new ISODate("1991-06-22"), gender: 'M', userMetadata: "inactive" } )
db.user.insert( { name: "Janice", date_of_birth: new ISODate("1995-09-04"), gender: 'F', userMetadata: { "shoeSize": 8, "likes": [ "horses", "dogs" ] } } )
db.user.insert( { name: "Peter", date_of_birth: new ISODate("2004-01-25"), gender: 'M', userMetadata: { "drivingLicense": { class: "A", "expirationDate": new ISODate("2030-05-05") } } } )

查看数据

> db.user.find()
[
  {
    _id: ObjectId('658927f4bad8d080878a999e'),
    name: 'John',
    date_of_birth: ISODate('2001-02-05T00:00:00.000Z'),
    gender: 'M',
    userMetadata: { likes: [ 'dogs', 'cats' ] }
  },
  {
    _id: ObjectId('658927f4bad8d080878a999f'),
    name: 'Marie',
    date_of_birth: ISODate('2008-03-12T00:00:00.000Z'),
    gender: 'F',
    userMetadata: { dislikes: 'hamsters' }
  },
  {
    _id: ObjectId('658927f4bad8d080878a99a0'),
    name: 'Tom',
    date_of_birth: ISODate('1998-12-23T00:00:00.000Z'),
    gender: 'M',
    userMetadata: { age: 25 }
  },
  {
    _id: ObjectId('658927f5bad8d080878a99a1'),
    name: 'Adrian',
    date_of_birth: ISODate('1991-06-22T00:00:00.000Z'),
    gender: 'M',
    userMetadata: 'inactive'
  },
  {
    _id: ObjectId('658927f5bad8d080878a99a2'),
    name: 'Janice',
    date_of_birth: ISODate('1995-09-04T00:00:00.000Z'),
    gender: 'F',
    userMetadata: { shoeSize: 8, likes: [ 'horses', 'dogs' ] }
  },
  {
    _id: ObjectId('658927f6bad8d080878a99a3'),
    name: 'Peter',
    date_of_birth: ISODate('2004-01-25T00:00:00.000Z'),
    gender: 'M',
    userMetadata: {
      drivingLicense: {
        class: 'A',
        expirationDate: ISODate('2030-05-05T00:00:00.000Z')
      }
    }
  }
]

metaData 子文档包含不同的字段。但所有这些字段都没有索引。

假设数据集包含几百万个文档,如何才能在不触发全数据集扫描的情况下,检索出具有特定驾驶执照类别或特定鞋码的所有用户呢?我们可以使用特殊语法 $** 在 userMetadata 字段上创建通配符索引。

abce> db.user.createIndex({ "userMetadata.$**" : 1 })
userMetadata.$**_1
abce> db.user.getIndexes()
[
  { v: 2, key: { _id: 1 }, name: '_id_' },
  { v: 2, key: { 'userMetadata.$**': 1 }, name: 'userMetadata.$**_1' }
]
abce> 
]

这样,MongoDB 就会为 userMetadata 中的每个字段和任何数成员在索引中创建一个条目。

 

现在,我们可以利用该索引执行任何类型的查询。

abce> db.user.find({ "userMetadata.likes": "dogs" })
[
  {
    _id: ObjectId('658927f4bad8d080878a999e'),
    name: 'John',
    date_of_birth: ISODate('2001-02-05T00:00:00.000Z'),
    gender: 'M',
    userMetadata: { likes: [ 'dogs', 'cats' ] }
  },
  {
    _id: ObjectId('658927f5bad8d080878a99a2'),
    name: 'Janice',
    date_of_birth: ISODate('1995-09-04T00:00:00.000Z'),
    gender: 'F',
    userMetadata: { shoeSize: 8, likes: [ 'horses', 'dogs' ] }
  }
]
abce> db.user.find({ "userMetadata.likes": "dogs" }).explain()
{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'abce.user',
    indexFilterSet: false,
    parsedQuery: { 'userMetadata.likes': { '$eq': 'dogs' } },
    queryHash: 'E2BC0D70',
    planCacheKey: '7C6EEF39',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      inputStage: {
        stage: 'IXSCAN',
        keyPattern: { '$_path': 1, 'userMetadata.likes': 1 },
        indexName: 'userMetadata.$**_1',
        isMultiKey: true,
        multiKeyPaths: {
          '$_path': [],
          'userMetadata.likes': [ 'userMetadata.likes' ]
        },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: {
          '$_path': [ '["userMetadata.likes", "userMetadata.likes"]' ],
          'userMetadata.likes': [ '["dogs", "dogs"]' ]
        }
      }
    },
    rejectedPlans: []
  },
  command: {
    find: 'user',
    filter: { 'userMetadata.likes': 'dogs' },
    '$db': 'abce'
  },
  serverInfo: {
    host: 'test',
    port: 27017,
    version: '6.0.12',
    gitVersion: '21e6e8e11a45dfbdb7ca6cf95fa8c5f859e2b118'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
  },
  ok: 1
}
abce> 

通过关键字 IXSCAN 可以检查一下查询是否用到了索引。

 

同理,下面的查询也可以从刚才创建的索引受益:

db.user.find( { "userMetadata.age" : { $gt: 20 }  } )
db.user.find( { "userMetadata": "inactive" } )
db.user.find( { "userMetadata.drivingLicense.class": "A", "userMetadata.drivingLicense.expirationDate": { $lt: ISODate("2032-01-01") } } )
db.user.find( { "userMetadata.shoeSize": 8})

 

在整个文档上创建通配符索引

在整个文档上创建通配符索引如何?这可行吗?

是的,可以。如果我们事先对将在集合中获得的文档一无所知,我们就可以这样做。

有一种特殊的语法可以做到这一点。在不指定字段名的情况下,再次使用 $**。

abce> db.user.createIndex( { "$**" : 1 } )
$**_1
abce>

 

同样可以执行一下刚才执行过的查询。可以看到所有列都被索引了:

abce> db.user.find( { name: "Marie" } )
[
  {
    _id: ObjectId('658927f4bad8d080878a999f'),
    name: 'Marie',
    date_of_birth: ISODate('2008-03-12T00:00:00.000Z'),
    gender: 'F',
    userMetadata: { dislikes: 'hamsters' }
  }
]
abce> db.user.find( { name: "Marie" } ).explain()
{
  explainVersion: '1',
  queryPlanner: {
    namespace: 'abce.user',
    indexFilterSet: false,
    parsedQuery: { name: { '$eq': 'Marie' } },
    queryHash: '64908032',
    planCacheKey: 'A6C0273F',
    maxIndexedOrSolutionsReached: false,
    maxIndexedAndSolutionsReached: false,
    maxScansToExplodeReached: false,
    winningPlan: {
      stage: 'FETCH',
      inputStage: {
        stage: 'IXSCAN',
        keyPattern: { '$_path': 1, name: 1 },
        indexName: '$**_1',
        isMultiKey: false,
        multiKeyPaths: { '$_path': [], name: [] },
        isUnique: false,
        isSparse: false,
        isPartial: false,
        indexVersion: 2,
        direction: 'forward',
        indexBounds: {
          '$_path': [ '["name", "name"]' ],
          name: [ '["Marie", "Marie"]' ]
        }
      }
    },
    rejectedPlans: []
  },
  command: { find: 'user', filter: { name: 'Marie' }, '$db': 'abce' },
  serverInfo: {
    host: 'test',
    port: 27017,
    version: '6.0.12',
    gitVersion: '21e6e8e11a45dfbdb7ca6cf95fa8c5f859e2b118'
  },
  serverParameters: {
    internalQueryFacetBufferSizeBytes: 104857600,
    internalQueryFacetMaxOutputDocSizeBytes: 104857600,
    internalLookupStageIntermediateDocumentMaxSizeBytes: 104857600,
    internalDocumentSourceGroupMaxMemoryBytes: 104857600,
    internalQueryMaxBlockingSortMemoryUsageBytes: 104857600,
    internalQueryProhibitBlockingMergeOnMongoS: 0,
    internalQueryMaxAddToSetBytes: 104857600,
    internalDocumentSourceSetWindowFieldsMaxMemoryBytes: 104857600
  },
  ok: 1
}
abce>  

 

优点和缺点

通配符索引的优点不言而喻,那就是它具有极大的灵活性。只需索引所有内容,甚至是你意想不到的内容。

缺点在于索引的大小。索引如果能被内存缓存,就能发挥最大功效。如果我们无法控制(或无法预见)我们创建的数据量,通配符索引的大小就会爆炸。

测试的数据集非常小,所以不用担心这些数字。但想想如果是非常大的集合,会发生什么情况。索引的大小可能会失控。

可以使用一个简单的技巧来增加集合的大小。运行以下语句,随时将文档数量翻倍。根据你想要的文档数量,执行八次或十次,或者更多。

db.user.find( {}, {_id:0}).forEach(function (doc) { db.user.insertOne(doc); } )

通配符索引一开始的好处是让事情变得更灵活,但最后却成为性能的严重瓶颈,导致更多的内存使用和交换。此外,大多数情况下,最频繁的查询只使用几个字段。使用通配符索引并不总是有意义的。

 

标签:index,name,利与弊,通配符,db,索引,user,userMetadata,wildcard
From: https://www.cnblogs.com/abclife/p/17926798.html

相关文章

  • router下的 index.js 对路由进行分析
    //引入vueimportVuefrom'vue'//引入vue-routerimportRouterfrom'vue-router'//路由就是一个插件需要useVue.use(Router)//引入layout组件很重要//一级路由出口匹配layout组件/*Layout*/importLayoutfrom'@/layout'/*RouterModules*///引入其......
  • ES 进行数据迁移 reindex (加速)
    https://www.cnblogs.com/ititit111222333/p/16382943.html 修改批量大小值POST_reindex{"source":{"index":"源索引名称","size":5000},"dest":{"index":"迁移的索引......
  • Python - pandas 报错:ValueError: 'HIS_批准文号' is both an index level and a colu
    问题描述file:[Terminal]ValueError:'HIS_批准文号'isbothanindexlevelandacolumnlabel,whichisambiguous.ValueError:cannotinsert招采_批准文号,alreadyexists有这两个错误,使用函数merge合并的时候出现第一个错误,将两个DataFrame的索引reset_index......
  • 控制台打印时显示的文件来源没有显示.vue文件,而是出现了一堆index.js??clonedRuleSet-
    控制台打印时显示的文件来源没有显示.vue文件,而是出现了一堆index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-optio…,看不出来打印的语句来自哪个vue组件检查发现edge打印显示正常,谷歌打印是以上这样的,谷歌设置一下配置 ......
  • 使用FORCE INDEX强制使用索引。
    优化前SELECTm.id,m.channel_id,m.sender_id,m.create_at,m.type,m.content,m.file_id,m.create_at,u.name,u.nickname,u.online_status,u.avatar_url,u.name_first_letter,f.nameASfile_name,f.extension,f.size,f.mime_typ,f.width,f.height,f.path,f.thumb_width,f.thumb......
  • MYSQL优化之索引(index)
    MYSQL优化之索引(index)-语法认知篇一、索引概述1.什么是索引索引是一种单独的、存储在磁盘上的数据库结构,包含对数据表中所有记录的引用指针。它的作用就相当于书籍的目录,可以加快对数据的查询速度2.索引的优点可以大大加快数据的检索速度可以保证数据库表中每行数据的唯......
  • Excel-最强函数搭档INDEX&MATCH(指数+寻找)
    1、VLOOKUP函数的缺点有一个很大的缺点就是如果要搜寻的栏位不是在表格的最左侧,或是表格采用了横向排列的话,无用武之地。如果是横向表格,而要查询的资料行也恰巧是第一行的话,可以使用HLOOKUP函数,Hlookup与vlookup类似,只是查询方向不同=HOOKUP(被查询值,查询的范围,要传回的行数)困境......
  • fadeIndex
    <body><styletype="text/css">*{margin:0;padding:0;}ul{height:30px;overflow:hidden;width:200px;margin:50pxauto;border:1pxsolidred;}li{......
  • 无涯教程-Java - String substring(int beginIndex)函数
    从beginIndex索引处开始截取字符串。Stringsubstring-语法publicStringsubstring(intbeginIndex)这是参数的详细信息-beginIndex  -  包含开始索引。Stringsubstring-返回值指定的子字符串。Stringsubstring-示例importjava.io.*;publicclassTest......
  • 无涯教程-Java - String substring(int beginIndex, int endIndex)函数
    截取beginIndex索引开始到endIndex结束之间的字符串内容。Stringsubstring-语法这是此方法的语法-publicStringsubstring(intbeginIndex,intendIndex)这是参数的详细信息-beginIndex - 包含开始索引。endIndex   - 不包含结束索引。Stringsubstri......