索引通常能够极大的提高查询的效率,如果没有索引,MongoDB 在读取数据时必须扫描集合中的每个文件并选取那些符合查询条件的记录。
索引最常用的比喻就是书籍的目录,查询索引就像查询一本书的目录。本质上目录是将书中一小部分内容信息(比如题目)和内容的位置信息(页码)共同构成,而由于信息量小(只有题目),所以我们可以很快找到我们想要的信息片段,再根据页码找到相应的内容。同样索引也是只保留某个域的一部分信息(建立了索引的 field 的信息),以及对应的文档的位置信息。
假设我们有如下文档(每行的数据在 MongoDB 中是存在于一个 Document 当中)
| 姓名 | id | 部门 | city | score |
|---|---|---|---|---|
| 张三 | 2 | 开发部 | 北京 | 90 |
| 李四 | 1 | 测试部 | 上海 | 70 |
| 王五 | 3 | 运维部 | 河北 | 60 |
假如我们想找 id 为 2 的 document (即张三的记录),如果没有索引,我们就需要扫描整个数据表,然后找出所有 id 为 2 的 document。当数据表中有大量 documents 的时候,这个查询时间就会很长(从磁盘上查找数据还涉及大量的 IO 操作)。
此时建立索引后会有什么变化呢?MongoDB 会将 id 数据拿出来建立索引数据,如下:
| 索引值 | 位置 |
|---|---|
| 1 | 第二行 |
| 2 | 第一行 |
| 3 | 第三行 |
此时,即可根据索引值快速得到原始数据的具体位置,从而获取完整的原始数据。
这样我们就可以通过扫描这个小表找到 document 对应的位置。
查找过程示意图如下:
索引为什么这么快:
为什么这样速度会快呢?这主要有几方面的因素
当然,事物总有其两面性,在提升查询速度的同时,由于要建立索引,所以写入操作时就需要额外的添加索引的操作,这必然会影响写入的性能,所以当有大量写操作而读操作比较少的时候,且对读操作性能不需要考虑的时候,就不适合建立索引。当然,目前大多数互联网应用都是读操作远大于写操作,因此建立索引很多时候是非常划算和必要的操作。
MongoDB 中的
explain()函数可以帮助我们查看查询相关的信息,这有助于我们快速查找到搜索瓶颈进而解决它,我们接下来就看看explain()的一些用法及其查询结果的含义。
先来看一个基本用法:
db.zips.find({"pop":99999}).explain()
直接跟在 find() 函数后面,表示查看 find() 函数的执行计划,结果如下:
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "zips-db.zips",
"indexFilterSet" : false,
"parsedQuery" : {
"pop" : {
"$eq" : 99999
}
},
"queryHash" : "891A44E4",
"planCacheKey" : "2D13A19E",
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"pop" : 1
},
"indexName" : "pop_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"pop" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : true,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"pop" : [
"[99999.0, 99999.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"serverInfo" : {
"host" : "linux30",
"port" : 27017,
"version" : "4.4.12",
"gitVersion" : "51475a8c4d9856eb1461137e7539a0a763cc85dc"
},
"ok" : 1
}
返回结果包含两大块信息,一个是 queryPlanner,即查询计划,还有一个是 serverInfo,即 MongoDB 服务的一些信息。
那么这里涉及到的参数比较多,我们来一一看一下:
| 参数 | 含义 |
|---|---|
| plannerVersion | 查询计划版本 |
| namespace | 要查询的集合 |
| indexFilterSet | 是否使用索引 |
| parsedQuery | 查询条件,此处为 x=1 |
| winningPlan | 最佳执行计划 |
| stage | 查询方式,常见的有 COLLSCAN / 全表扫描、IXSCAN / 索引扫描、FETCH / 根据索引去检索文档、SHARD_MERGE / 合并分片结果、IDHACK / 针对_id 进行查询 |
| filter | 过滤条件 |
| direction | 搜索方向 |
| rejectedPlans | 拒绝的执行计划 |
| serverInfo | MongoDB 服务器信息 |
explain()也接收不同的参数,通过设置不同参数我们可以查看更详细的查询计划。
是默认参数,添加 queryPlanner 参数的查询结果就是我们上文看到的查询结果,这里不再赘述。
会返回最佳执行计划的一些统计信息,如下:
db.zips.find({"pop":99999}).explain("executionStats")
我们发现增加了一个 executionStats 的字段列的信息
{
"queryPlanner" : {
"plannerVersion" : 1,
"namespace" : "zips-db.zips",
"indexFilterSet" : false,
"parsedQuery" : {
"pop" : {
"$eq" : 99999
}
},
"winningPlan" : {
"stage" : "FETCH",
"inputStage" : {
"stage" : "IXSCAN",
"keyPattern" : {
"pop" : 1
},
"indexName" : "pop_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"pop" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : true,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"pop" : [
"[99999.0, 99999.0]"
]
}
}
},
"rejectedPlans" : [ ]
},
"executionStats" : {
"executionSuccess" : true,
"nReturned" : 0,
"executionTimeMillis" : 1,
"totalKeysExamined" : 0,
"totalDocsExamined" : 0,
"executionStages" : {
"stage" : "FETCH",
"nReturned" : 0,
"executionTimeMillisEstimate" : 0,
"works" : 1,
"advanced" : 0,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"docsExamined" : 0,
"alreadyHasObj" : 0,
"inputStage" : {
"stage" : "IXSCAN",
"nReturned" : 0,
"executionTimeMillisEstimate" : 0,
"works" : 1,
"advanced" : 0,
"needTime" : 0,
"needYield" : 0,
"saveState" : 0,
"restoreState" : 0,
"isEOF" : 1,
"keyPattern" : {
"pop" : 1
},
"indexName" : "pop_1",
"isMultiKey" : false,
"multiKeyPaths" : {
"pop" : [ ]
},
"isUnique" : false,
"isSparse" : false,
"isPartial" : true,
"indexVersion" : 2,
"direction" : "forward",
"indexBounds" : {
"pop" : [
"[99999.0, 99999.0]"
]
},
"keysExamined" : 0,
"seeks" : 1,
"dupsTested" : 0,
"dupsDropped" : 0
}
}
},
"serverInfo" : {
"host" : "linux30",
"port" : 27017,
"version" : "4.4.12",
"gitVersion" : "51475a8c4d9856eb1461137e7539a0a763cc85dc"
},
"ok" : 1
}
这里除了我们上文介绍到的一些参数之外,还多了 executionStats 参数,含义如下:
| 参数 | 含义 |
|---|---|
| executionSuccess | 是否执行成功 |
| nReturned | 返回的结果数 |
| executionTimeMillis | 执行耗时 |
| totalKeysExamined | 索引扫描次数 |
| totalDocsExamined | 文档扫描次数 |
| executionStages | 这个分类下描述执行的状态 |
| stage | 扫描方式,具体可选值与上文的相同 |
| nReturned | 查询结果数量 |
| executionTimeMillisEstimate | 预估耗时 |
| works | 工作单元数,一个查询会分解成小的工作单元 |
| advanced | 优先返回的结果数 |
| docsExamined | 文档检查数目,与 totalDocsExamined 一致 |
allPlansExecution:用来获取所有执行计划,结果参数基本与上文相同。
在 MySQL 中,慢查询日志是经常作为我们优化查询的依据,那在 MongoDB 中是否有类似的功能呢?答案是肯定的,那就是开启 Profiling 功能。该工具在运行的实例上收集有关 MongoDB 的写操作,游标,数据库命令等,可以在数据库级别开启该工具,也可以在实例级别开启。该工具会把收集到的所有都写入到 system.profile集合中,该集合是一个 capped collection。
慢查询日志一般作为优化步骤里的第一步。通过慢查询日志,定位每一条语句的查询时间。比如超过了 200ms,那么查询超过 200ms 的语句需要优化。然后它通过 explain () 解析影响行数是不是过大,所以导致查询语句超过 200ms。
所以优化步骤一般就是:
Profiling 级别
0:关闭,不收集任何数据。
1:收集慢查询数据,默认是100毫秒。
2:收集所有数据
数据库设置
登录需要开启慢查询的数据库
use zips-db
查看慢查询状态
db.getProfilingStatus()
设置慢查询级别
db.setProfilingLevel(2)
如果不需要收集所有慢日志,只需要收集小于 100ms 的慢日志可以使用如下命令
db.setProfilingLevel(1,200)
注意:
全局设置
在 mongoDB 启动的时候加入如下参数
mongod --profile=1 --slowms=200
或在配置文件里添加 2 行:
profile = 1
slowms = 200
这样就可以针对所有数据库进行监控慢日志了
关闭 Profiling
使用如下命令可以关闭慢日志
db.setProfilingLevel(0)
Profiling 功能肯定是会影响效率的,但是不太严重,原因是其使用的 system.profile 来记录,而 system.profile 是一个 capped collection, 这种 collection 在操作上有一些限制和特点,但是效率更高。
通过 db.system.profile.find () 查看当前所有的慢查询日志
db.system.profile.find()
参数含义:
{
"op" : "query", #操作类型,有insert、query、update、remove、getmore、command
"ns" : "onroad.route_model", #操作的集合
"query" : {
"$query" : {
"user_id" : 314436841,
"data_time" : {
"$gte" : 1436198400
}
},
"$orderby" : {
"data_time" : 1
}
},
"ntoskip" : 0, #指定跳过skip()方法 的文档的数量。
"nscanned" : 2, #为了执行该操作,MongoDB在 index 中浏览的文档数。 一般来说,如果 nscanned 值高于 nreturned 的值,说明数据库为了找到目标文档扫描了很多文档。这时可以考虑创建索引来提高效率。
"nscannedObjects" : 1, #为了执行该操作,MongoDB在 collection中浏览的文档数。
"keyUpdates" : 0, #索引更新的数量,改变一个索引键带有一个小的性能开销,因为数据库必须删除旧的key,并插入一个新的key到B-树索引
"numYield" : 1, #该操作为了使其他操作完成而放弃的次数。通常来说,当他们需要访问还没有完全读入内存中的数据时,操作将放弃。这使得在MongoDB为了放弃操作进行数据读取的同时,还有数据在内存中的其他操作可以完成
"lockStats" : { #锁信息,R:全局读锁;W:全局写锁;r:特定数据库的读锁;w:特定数据库的写锁
"timeLockedMicros" : { #该操作获取一个级锁花费的时间。对于请求多个锁的操作,比如对 local 数据库锁来更新 oplog ,该值比该操作的总长要长(即 millis )
"r" : NumberLong(1089485),
"w" : NumberLong(0)
},
"timeAcquiringMicros" : { #该操作等待获取一个级锁花费的时间。
"r" : NumberLong(102),
"w" : NumberLong(2)
}
},
"nreturned" : 1, // 返回的文档数量
"responseLength" : 1669, // 返回字节长度,如果这个数字很大,考虑值返回所需字段
"millis" : 544, #消耗的时间(毫秒)
"execStats" : { #一个文档,其中包含执行 查询 的操作,对于其他操作,这个值是一个空文件, system.profile.execStats 显示了就像树一样的统计结构,每个节点提供了在执行阶段的查询操作情况。
"type" : "LIMIT", ##使用limit限制返回数
"works" : 2,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 1, #是否为文件结束符
"children" : [
{
"type" : "FETCH", #根据索引去检索指定document
"works" : 1,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 0,
"alreadyHasObj" : 0,
"forcedFetches" : 0,
"matchTested" : 0,
"children" : [
{
"type" : "IXSCAN", #扫描索引键
"works" : 1,
"yields" : 1,
"unyields" : 1,
"invalidates" : 0,
"advanced" : 1,
"needTime" : 0,
"needFetch" : 0,
"isEOF" : 0,
"keyPattern" : "{ user_id: 1.0, data_time: -1.0 }",
"boundsVerbose" : "field #0['user_id']: [314436841, 314436841], field #1['data_time']: [1436198400, inf.0]",
"isMultiKey" : 0,
"yieldMovedCursor" : 0,
"dupsTested" : 0,
"dupsDropped" : 0,
"seenInvalidated" : 0,
"matchTested" : 0,
"keysExamined" : 2,
"children" : [ ]
}
]
}
]
},
"ts" : ISODate("2015-10-15T07:41:03.061Z"), #该命令在何时执行
"client" : "10.10.86.171", #链接ip或则主机
"allUsers" : [
{
"user" : "martin_v8",
"db" : "onroad"
}
],
"user" : "martin_v8@onroad"
}
分析:
如果发现 millis 值比较大,那么就需要作优化。
system.profile 补充:
‘type’的返回参数说明
COLLSCAN #全表扫描
IXSCAN #索引扫描
FETCH #根据索引去检索指定document
SHARD_MERGE #将各个分片返回数据进行merge
SORT #表明在内存中进行了排序(与老版本的scanAndOrder:true一致)
LIMIT #使用limit限制返回数
SKIP #使用skip进行跳过
IDHACK #针对_id进行查询
SHARDING_FILTER #通过mongos对分片数据进行查询
COUNT #利用db.coll.explain().count()之类进行count运算
COUNTSCAN #count不使用Index进行count时的stage返回
COUNT_SCAN #count使用了Index进行count时的stage返回
SUBPLA #未使用到索引的$or查询的stage返回
TEXT #使用全文索引进行查询时候的stage返回
PROJECTION #限定返回字段时候stage的返回
对于普通查询,我们最希望看到的组合有这些
Fetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)
PROJECTION+ixscan
SHARDING_FILTER+ixscan
不希望看到包含如下的 type
COLLSCAN(全表扫),SORT(使用sort但是无index),不合理的SKIP,SUBPLA(未用到index的$or)
我发现自己需要这个。假设cart是一个包含用户列表的模型。defindex_of_itemcart.users.each_with_indexdo|u,i|ifu==current_userreturniendend获取此类关联索引的更简单方法是什么? 最佳答案 indexArray上的方法与您的index_of_item方法相同,例如cart.users.index(current_user)返回数组中第一个对象的索引==给obj。如果未找到匹配项,则返回nil。 关于ruby-on-
因此,当我遵循MichaelHartl的RubyonRails教程时,我注意到在用户表中,我们为:email属性添加了一个唯一索引,以提高find的效率方法,因此它不会逐行搜索。到目前为止,我们一直在根据情况使用find_by_email和find_by_id进行搜索。然而,我们从未为:id属性设置索引。:id是否自动索引,因为它在默认情况下是唯一的并且本质上是顺序的?或者情况并非如此,我应该为:id搜索添加索引吗? 最佳答案 大多数数据库(包括sqlite,这是RoR中的默认数据库)会自动索引主键,对于RailsMigration
假设我有一个可枚举对象enum,现在我想获取第三个项目。我知道一种通用方法是转换成数组,然后使用索引访问,如:enum.to_a[2]但这种方式会创建一个临时数组,效率可能很低。现在我使用:enum.each_with_index{|v,i|breakvifi==2}但这非常丑陋和多余。执行此操作最有效的方法是什么? 最佳答案 你可以使用take剥离前三个元素,然后剥离last从take给你的数组中获取第三个元素:third=enum.take(3).last如果您根本不想生成任何数组,那么也许:#Ifenumisn'tanEnum
在我的场景中,Logstash收到的系统日志行的“时间戳”是UTC,我们在Elasticsearch输出中使用事件“时间戳”:output{elasticsearch{embedded=>falsehost=>localhostport=>9200protocol=>httpcluster=>'elasticsearch'index=>"syslog-%{+YYYY.MM.dd}"}}我的问题是,在UTC午夜,Logstash在外时区(GMT-4=>America/Montreal)结束前将日志发送到不同的索引,并且索引在20小时(晚上8点)之后没有日志,因为“时间戳”是UTC。我们已
我想从特定索引开始遍历数组。我该怎么做?myj.eachdo|temp|...end 最佳答案 执行以下操作:your_array[your_index..-1].eachdo|temp|###end 关于ruby-从特定索引开始迭代数组,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/44151758/
我一直在努力学习如何处理由数组组成的数组。假设我有这个数组:my_array=[['ORANGE',1],['APPLE',2],['PEACH',3]我将如何找到包含'apple'的my_array索引并删除该索引(删除子数组['APPLE',2]因为'apple'包含在该索引的数组中)?谢谢-我非常感谢这里的帮助。 最佳答案 您可以使用Array.select过滤掉项目:>>a=[['ORANGE',1],['APPLE',2],['PEACH',3]]=>[["ORANGE",1],["APPLE",2],["PEACH",3
我想使用部分字符串搜索数组,然后获取找到该字符串的索引。例如:a=["Thisisline1","Wehaveline2here","andfinallyline3","potato"]a.index("potato")#thisreturns3a.index("Wehave")#thisreturnsnil使用a.grep将返回完整的字符串,使用a.any?将返回正确的true/false语句,但都不会返回匹配的索引找到了,或者至少我不知道该怎么做。我正在编写一段代码,该代码读取文件、查找特定header,然后返回该header的索引,以便它可以将其用作future搜索的偏移量。如果
如何在rakedb:migrate:status中删除带有“**NOFILE**”的迁移ID列表?例如:StatusMigrationIDMigrationName--------------------------------------------------up20131017204224Createusersup20131218005823**********NOFILE**********up20131218011334**********NOFILE**********我不明白为什么当我自己手动删除它时它仍然保留旧的迁移文件,因为我正在研究迁移的工作原理。这是为了记录吗?但
一、什么是MQTT协议MessageQueuingTelemetryTransport:消息队列遥测传输协议。是一种基于客户端-服务端的发布/订阅模式。与HTTP一样,基于TCP/IP协议之上的通讯协议,提供有序、无损、双向连接,由IBM(蓝色巨人)发布。原理:(1)MQTT协议身份和消息格式有三种身份:发布者(Publish)、代理(Broker)(服务器)、订阅者(Subscribe)。其中,消息的发布者和订阅者都是客户端,消息代理是服务器,消息发布者可以同时是订阅者。MQTT传输的消息分为:主题(Topic)和负载(payload)两部分Topic,可以理解为消息的类型,订阅者订阅(Su
TCL脚本语言简介•TCL(ToolCommandLanguage)是一种解释执行的脚本语言(ScriptingLanguage),它提供了通用的编程能力:支持变量、过程和控制结构;同时TCL还拥有一个功能强大的固有的核心命令集。TCL经常被用于快速原型开发,脚本编程,GUI和测试等方面。•实际上包含了两个部分:一个语言和一个库。首先,Tcl是一种简单的脚本语言,主要使用于发布命令给一些互交程序如文本编辑器、调试器和shell。由于TCL的解释器是用C\C++语言的过程库实现的,因此在某种意义上我们又可以把TCL看作C库,这个库中有丰富的用于扩展TCL命令的C\C++过程和函数,所以,Tcl是