深入浅出MongoDB(七)
文章目录
-
- 深入浅出MongoDB(七)
-
- 查询优化
- 分析性能
- 优化查询性能
-
- 创建索引以支持查询
- 限制查询结果数量以减少网络请求
- 使用投影仅返回必要的数据
- [使用hint选择特定索引](#使用hint选择特定索引)
- 使用增量运算符执行服务器端操作
查询优化
创建索引以支持读取操作
- 当应用程序对特定字段或一组字段的集合进行查询时,查询字段上的索引或字段集的复合索引可能会阻止查询操作扫描整个集合以查找和返回查询结果。
shell
# 示例查询inventory集合中的type字段
var typeValue = <someUserInput>;
db.inventory.find({type: typeValue});
# 要提高查询的性能,可以给type字段添加升序或降序索引,防止对type的查询扫描整个集合
db.inventory.createIndex({type: 1})
# inventory中的文档结构如下
[
{
"_id": 1,
"item": "f1",
"quantity": 500,
"type": "food"
}
]
查询选择性
- 查询选择性是指查询谓词排除或过滤出集合中文档的程序,可以确定查询能够有效地使用索引,或者根本不适用索引。
- 选择性更强的查询匹配的文档比例更小;选择性较低的查询会匹配更大比例的文档,无法有效的使用索引或者根本不适用索引。
- 正则表达式的选择性取决于表达式本身。
覆盖查询
- 覆盖查询是可以完全使用索引来满足并且不必检查任何文档的查询。
- 满足以下所以条件时,索引会覆盖查询
- 查询中的所有字段都是索引的一部分,并且结果中所有的字段都位于同一索引中;
- 查询中没有字段等于null(
field:null
或field:{$eq:null}
)
shell
# 在inventory集合的type和item字段建立索引
db.inventory.createIndex({type: 1, item: 1})
# 查询type和item字段并且仅返回item字段,为了使索引覆盖查询,投影文档必须指定_id:0从结果中排除_id字段
db.inventory.find(
{type: 'food', item: '/^c/'},
{item: 1, _id: 0}
)
- 索引可以涵盖对嵌入式文档中字段的查询,给嵌入式文档中的字段编制索引,使用点符号
shell
# 示例集合userdata形式如下
{_id: 1, user: {login: 'tester'}}
# 包含以下索引可以覆盖查询
{'user.login': 1}
db.userdata.find({'user.login': 'tester'}, {'user.login': 1, _id: 0})
- 多键索引如果要追踪哪个或哪些字段致使其成为多键,则其可涵盖对非数组字段的查询。多键索引不能涵盖对数组字段的查询。
- 由于索引包含查询所需的所有字段,所以mongodb可以只是用索引来匹配查询条件并返回结果。仅查询索引比查询索引之外的文档要快的多。索引键通常小于它们编目的文档,并且索引通常在RAM中可用或按顺序存储在磁盘上。
- 带索引字段的限制包括地理空间索引不能覆盖查询,多键索引不能涵盖对数组字段的查询。在mongos上运行时,如果索引包含分片键,则索引只能涵盖对分片集合的查询。
分析性能
使用数据库分析器评估对数据库的操作
-
mongodb提供了一个数据库分析器,可以显示针对数据库的每个操作的性能特征。使用分析器查找任何运行缓慢的查询或写入操作。分析器对其手机的所有数据写入每个被分析数据库中的
system.profile
集合,这时一种固定大小集合。 -
读写操作的分析器条目和诊断日志消息包括以下内容
- queryHash帮助识别具有相同查询结构的慢速查询。
- planCacheKey提供对慢速查询的查询计划缓存的更多见解。
- 副本集的从节点会记录应用事件超过慢操作域值的oplog条目。oplog消息包括以下内容。
- 在诊断日志中针对从节点记录;
- 记录在REPL组件下,该组件含有文本
applied op: <oplog entry> took <num>ms
; - 不依赖日志级别和分析级别,受
slowOpSampleRate
影响。
-
默认情况下,分析器为off关闭状态,我们可以按数据库或按实例启用分析器并设置分析级别。启用分析功能后,会影响数据库性能和磁盘使用情况。
-
分析级别
shell
等级0:分析器已关闭,因此不收集任何数据,这是默认的分析器级别。
等级1:分析器会收集用时超过slowms值或与某一筛选器匹配的操作的对应数据。
等级2:该分析器回收机所有操作的数据。
- 使用mongosh辅助程序
db.setProfilingLevel()
来启用分析。当启用分析时,设置分析级别大于0,分析器会将数据记录在system.profile
集合中。
shell
# slowms和sampleRate分析设置为全局设置,影响进程中的所有数据库
# profile命令或db.setProfilingLevel()设置时,在数据库级别设置分析级别和筛选器设置,对数据分析器level、slowms、sampleRate或filter所做的更改会记录在log file中,slowms和sampleRate分析设置为全局设置,设置后会影响进程中的所有数据库
db.setProfilingLevel(2)
# 默认情况下,慢操作域值为100ms,使用profile命令或db.setProfilingLevel()方法设置slowms的值,启动时使用命令行参数--slowms设置,在配置文件中设置slowOpThresholdMs的值
db.setProfilingLevel(1, {slowms: 20})
# 分析器记录耗时超过2s的query操作
db.setProfilingLevel(2, {filter:{op:"query", millis:{$gt:2000}}})
# 查看分析级别
db.getProfilingStatus()
# 禁用性能分析
db.setProfilingLevel(0)
- 查看分析器数据
shell
# 数据库分析器在system.profile集合中记录有关数据库操作的信息
# 查询system.profile集合中最近的10个日志条目
db.system.profile.find().limit(10).sort({ts:-1}).pretty()
# 返回除命令操作$cmd以外的所有操作
db.system.profile.find({op:{$ne:'command'}}).pretty()
# 返回某一时间范围的信息
db.system.profile.find({ts: {
$gt: new ISODate("2012-12-09T03:00:00Z"),
$lt: new ISODate("2012-12-09T03:40:00Z")
}}).pretty()
# 显示最近的五个事件
show profile
-
分析器开销
启用后分析会影响数据库性能,还会消耗磁盘空间,
system.profile
集合是一个固定大小集合,默认为1MB。
使用db.currentOp()
评估mongod操作
db.currentOp()
方法会报告mongod实例上运行的当前操作。
使用explain评估查询性能
-
我们可以在queryPlanner模式、executionStats模式或allPlansExecution模式下运行explain方法来控制返回的信息量。
-
explain结果将查询计划显示为阶段树,每个阶段都会传递其结果复制到父节点。叶节点访问权限集合或索引。内部节点操作由子节点产生的文档或索引键。节点是mongodb派生结果设立的最后阶段。阶段是对操作的描述。
- COLLSCAN用于集合扫描
- IXSCAN用于扫描索引键
- FETCH用于检索文档
- SHARD_MERGE用于合并来自分片的结果
- SHARDING_FILTER用于从分片中过滤掉孤立文档
shell
"winningPlan" : {
"stage" : <STAGE1>,
...
"inputStage" : {
"stage" : <STAGE2>,
...
"inputStage" : {
"stage" : <STAGE3>,
...
}
}
},
- queryPlanner信息详细说明了查询优化器选择的计划,以未分片的集合为例。
shell
"queryPlanner" : {
"plannerVersion" : <int>,
# 一个字符串,使用数据库名称和查询访问的集合,<database>.<collection>
"namespace" : <string>,
# 指定了是否对查询结构应用了索引筛选器
"indexFilterSet" : <boolean>,
"parsedQuery" : {
...
},
# 十六进制字符串,表示查询结构的哈希值
"queryHash" : <hexadecimal string>,
# 与此查询关联的计划缓存条目的键的哈希值
"planCacheKey" : <hexadecimal string>,
# 表明整个聚合管道操作已被优化掉,改用查询计划执行阶段树来实现
"optimizedPipeline" : <boolean>, // Starting in MongoDB 4.2, only appears if true
# 详细说明查询优化器所选计划的文档
"winningPlan" : {
# 阶段名称
"stage" : <STAGE1>,
...
# 描述该子阶段的文档
"inputStage" : {
"stage" : <STAGE2>,
...
"inputStage" : {
...
}
}
},
# 查询优化器考虑和拒绝的候选计划数组
"rejectedPlans" : [
<candidate plan 1>,
...
]
}
- executionStats信息详细说明了获胜计划的执行情况,以未分片的集合为例。
shell
"executionStats" : {
"executionSuccess" : <boolean>,
# 获胜查询计划返回的文档数
"nReturned" : <int>,
# 选择查询计划和执行查询所需的总时间
"executionTimeMillis" : <int>,
# 扫描的索引项数
"totalKeysExamined" : <int>,
# 查询执行过程中检查的文档数量
"totalDocsExamined" : <int>,
# 以阶段树的形式详细说明获胜计划的执行情况
"executionStages" : {
"stage" : <STAGE1>
"nReturned" : <int>,
# 查询执行的估计时间
"executionTimeMillisEstimate" : <int>,
# 指定查询执行阶段的工作单元数量
"works" : <int>,
# 此阶段返回到其父阶段的中间结果的数量
"advanced" : <int>,
# 未将中间结果推进到其父阶段的工作周期数
"needTime" : <int>,
# 存储层请求查询阶段暂停处理并让出其锁的次数
"needYield" : <int>,
# 查询阶段暂停处理并保存其当前执行状态的次数
"saveState" : <int>,
# 查询阶段恢复了已保存的执行状态的次数
"restoreState" : <int>,
# 指定执行阶段是否已到达流结束
"isEOF" : <boolean>,
...
"inputStage" : {
"stage" : <STAGE2>,
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
...
"inputStage" : {
...
}
}
},
"allPlansExecution" : [
{
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
"totalKeysExamined" : <int>,
"totalDocsExamined" :<int>,
"executionStages" : {
"stage" : <STAGEA>,
"nReturned" : <int>,
"executionTimeMillisEstimate" : <int>,
...
"inputStage" : {
"stage" : <STAGEB>,
...
"inputStage" : {
...
}
}
}
},
...
]
}
-
对于索引相交计划,结果将包括AND_SORTED阶段或AND_HASH阶段,以及详细说明索引的inputStages数组。
-
如果
$or
表达式使用索引,那么结果将包括OR阶段和一个详细说明索引的inputStagtes数组。 -
当explain在ExecutionStats或AllplansExecution详细模式下运行时,
$sort
和$group
阶段会有额外的输出。
阶段 | 字段 | 类型 | 说明 |
---|---|---|---|
$sort | totalDataSizeSortedBytesEstimate | long | $sort阶段处理的字节估计数 |
$sort | usedDisk | 布尔 | $osrt阶段是否已写入磁盘 |
$group | totalOutputDataSizeBytes | long | $group阶段输出的所有文档总大小的估计值 |
$group | usedDisk | 布尔 | $group阶段是否已写入磁盘 |
优化查询性能
创建索引以支持查询
- 对于常用查询,创建索引。如果查询会搜索多个字段,需要使用复合索引。扫描索引比扫描集合快得多,索引结构小于文档引用,并按顺序存储引用。索引还能提高定期对字段进行排序的查询的效率。
- 索引支持查询、更新操作和聚合管道的某些阶段。
限制查询结果数量以减少网络请求
- mongodb游标以多个文档群组的形式返回结果,通过排序操作和
limit()
方法集合使用。
使用投影仅返回必要的数据
- 如果只需要文档中的部分字段,就可以通过仅返回所需要的字段来实现更优性能。
使用$hint选择特定索引
- 大多数情况下,查询优化器会为特定操作选择最佳索引,但是我们可以使用
hint()
方法强制使用特定索引。
使用增量运算符执行服务器端操作
- 使用
$inc
运算符来递增或递减文档中的值。作为选择文档操作的替代方法,此运算符会在服务器端递增该字段的值,从而在客户端进行建议修改,然后将整个文档写入服务器。