共计 12083 个字符,预计需要花费 31 分钟才能阅读完成。
MongoDB 索引优化
- 作者: 博学谷狂野架构师
-
GitHub:GitHub 地址(有我精心筹备的 130 本电子书 PDF)
只分享干货、不吹水,让咱们一起加油!😄
索引简介
索引通常可能极大的进步查问的效率,如果没有索引,MongoDB 在读取数据时必须扫描汇合中的每个文件并选取那些合乎查问条件的记录。
什么是索引
索引最罕用的比喻就是书籍的目录,查问索引就像查问一本书的目录。实质上目录是将书中一小部分内容信息(比方题目)和内容的地位信息(页码)独特形成,而因为信息量小(只有题目),所以咱们能够很快找到咱们想要的信息片段,再依据页码找到相应的内容。同样索引也是只保留某个域的一部分信息(建设了索引的 field 的信息),以及对应的文档的地位信息。
假如咱们有如下文档(每行的数据在 MongoDB 中是存在于一个 Document 当中)
姓名 | id | 部门 | city | score |
---|---|---|---|---|
张三 | 2 | xxx | Beijing | 90 |
李四 | 1 | xxx | Shanghai | 70 |
王五 | 3 | xxx | guangzhou | 60 |
索引的作用
如果咱们想找 id 为 2 的 document(即张三的记录),如果没有索引,咱们就须要扫描整个数据表,而后找出所有为 2 的 document。当数据表中有大量 documents 的时候,这个工夫就会十分长(从磁盘上查找数据还波及大量的 IO 操作)。建设索引后会有什么变动呢?MongoDB 会将 id 数据拿进去建设索引数据,如下
索引值 | 地位 |
---|---|
1 | pos2 |
2 | pos1 |
3 | pos3 |
索引的工作原理
这样咱们就能够通过扫描这个小表找到 document 对应的地位。
查找过程示意图如下:
索引为什么这么快
为什么这样速度会快呢?这次要有几方面的因素
- 索引数据通过 B 树来存储,从而使得搜寻的工夫复杂度为 O(logdN)级别的 (d 是 B 树的度, 通常 d 的值比拟大,比方大于 100),比原先 O(N) 的复杂度大幅降落。这个差距是惊人的,以一个理论例子来看,假如 d =100,N= 1 亿,那么 O(logdN) = 8, 而 O(N)是 1 亿。是的,这就是算法的威力。
-
索引自身是在高速缓存当中,相比磁盘 IO 操作会有大幅的性能晋升。(须要留神的是,有的时候数据量十分大的时候,索引数据也会十分大,当大到超出内存容量的时候,会导致局部索引数据存储在磁盘上,这会导致磁盘 IO 的开销大幅减少,从而影响性能,所以务必要保障有足够的内存能容下所有的索引数据)
当然,事物总有其两面性,在晋升查问速度的同时,因为要建设索引,所以写入操作时就须要额定的增加索引的操作,这必然会影响写入的性能,所以当有大量写操作而读操作比拟少的时候,且对读操作性能不须要思考的时候,就不适宜建设索引。当然,目前大多数互联网利用都是读操作远大于写操作,因而建设索引很多时候是十分划算和必要的操作。
查看索引
索引是进步查问查问效率最无效的伎俩。索引是一种非凡的数据结构,索引以易于遍历的模式存储了数据的局部内容(如:一个特定的字段或一组字段值),索引会按肯定规定对存储值进行排序,而且索引的存储地位在内存中,所在从索引中检索数据会十分快。如果没有索引,MongoDB 必须扫描汇合中的每一个文档,这种扫描的效率非常低,尤其是在数据量较大时。
默认主键索引
在创立汇合期间,MongoDB 在 _id]
字段上 创立 惟一索引, 该索引可避免客户端插入两个具备雷同值的文档。您不能将此索引放在字段上。
查看索引
查看汇合索引
要返回汇合中所有索引的列表能够应用
db.collection.getIndexes()
查看现有索引
COPYdb.zips.getIndexes();
查看
zips
汇合的所有索引,咱们看到有一个默认的_id_
索引,并且是一个升序索引
查看数据库
若要列出数据库中所有汇合的所有索引,则需在 MongoDB 的 Shell 客户端中进行以下操作:
COPYdb.getCollectionNames().forEach(function(collection){indexes = db[collection].getIndexes();
print("Indexes for [" + collection + "]:" );
printjson(indexes);
});
这样能够列出本数据库的所有汇合的索引
索引罕用操作
创立索引
MongoDB 应用 createIndex() 办法来创立索引。
留神在 3.0.0 版本前创立索引办法为 db.collection.ensureIndex(),之后的版本应用了 db.collection.createIndex() 办法,ensureIndex() 还能用,但只是 createIndex() 的别名。
语法
createIndex()办法根本语法格局如下所示:
COPYdb.collection.createIndex(keys, options)
语法中 Key 值为你要创立的索引字段,1 为指定按升序创立索引,如果你想按降序来创立索引指定为 -1 即可。
COPYdb.zips.createIndex({"pop":1})
这样就依据
pop
字段创立了一个升序索引
索引参数
createIndex() 接管可选参数,可选参数列表如下
Parameter | Type | Description |
---|---|---|
background | Boolean | 建索引过程会阻塞其它数据库操作,background 可指定当前台形式创立索引,即减少“background”可选参数。“background”默认值为false。 |
unique | Boolean | 建设的索引是否惟一。指定为 true 创立惟一索引。默认值为false. |
name | string | 索引的名称。如果未指定,MongoDB 的通过连贯索引的字段名和排序程序生成一个索引名称。 |
dropDups | Boolean | 3.0+ 版本已废除。在建设惟一索引时是否删除重复记录, 指定 true 创立惟一索引。默认值为 false. |
sparse | Boolean | 对文档中不存在的字段数据不启用索引;这个参数须要特地留神,如果设置为 true 的话,在索引字段中不会查问出不蕴含对应字段的文档.。默认值为 false. |
expireAfterSeconds | integer | 指定一个以秒为单位的数值,实现 TTL 设定,设定汇合的生存工夫。 |
v | index version | 索引的版本号。默认的索引版本取决于 mongod 创立索引时运行的版本。 |
weights | document | 索引权重值,数值在 1 到 99,999 之间,示意该索引绝对于其余索引字段的得分权重。 |
default_language | string | 对于文本索引,该参数决定了停用词及词干和词器的规定的列表。默认为英语 |
language_override | string | 对于文本索引,该参数指定了蕴含在文档中的字段名,语言笼罩默认的 language,默认值为 language. |
示例
创立一个名称是
pop_union_index
的索引,依照pop
字段降序,并且在 10 秒后删除
COPYdb.zips.createIndex(
{"pop":-1},
{
"name":"pop_union_index",
"expireAfterSeconds":10
}
)
这样咱们就创立了一个索引
删除索引
MongoDB 提供的两种从汇合中删除索引的办法如下:
依据 name 删除
能够依据索引的名字进行索引删除
COPYdb.zips.dropIndex("loc_2d")
这样咱们就把一个索引删除了
依据字段删除
还能够依据字段进行删除
COPYdb.zips.dropIndex ({"pop" : 1})
删除汇合中
pop
字段升序的索引,这样就把这个索引删除了
删除所有索引
db.collection.dropIndexes()
能够把汇合所有索引删除
COPYdb.zips.dropIndexes()`
这样就把非默认的主键索引意外的索引索引删除了
MongoDB 索引类型
单键索引
MongoDB 为文档汇合中任何字段上的索引提供了残缺的反对。默认状况下,所有汇合在
_id
字段上都有一个索引,应用程序和用户能够增加其余索引来反对重要的查问和操作。
这个是最简略最罕用的索引类型,比方咱们上边的例子,为 id 建设一个独自的索引就是此种类型。
创立索引
咱们创立一个
pop
人数升序的索引
COPYdb.zips.createIndex({'pop': 1})
其中
{'pop': 1}
中的 1 示意升序,如果想设置倒序索引的话应用{'pop': -1}
可
查看执行打算
能够在查问中应用执行打算查看索引是否失效
COPYdb.zips.find({"pop":{$gt:10000}}).explain();
咱们发现索引曾经失效了
复合索引
复合索引 (Compound Indexes) 指一个索引蕴含多个字段,用法和单键索引基本一致。应用复合索引时要留神字段的程序,如下增加一个 name 和 age 的复合索引,name 正序,age 倒序,document 首先依照 name 正序排序,而后 name 雷同的 document 按 age 进行倒序排序。mongoDB 中一个复合索引最多能够蕴含 32 个字段。合乎索引的原理如下图所示:
上图查问索引的时候会先查问 userid,再查问 score,而后就能够找到对应的文档。
创立索引
咱们创立一个以
CUSHMAN
升序,state
降序的合乎索引
COPYdb.zips.createIndex({"city": 1,"state":-1})
这样咱们就把索引创立了
查看执行打算
COPYdb.zips.find({"city":"CUSHMAN","state":"NY"}).explain();
咱们看到咱们的查问走了索引
对于复合索引须要留神以下几点:
最左前缀法令
在 MySQL 中走前缀法令失效,在 mongodb 中查问同样失效
COPYdb.zips.find({"city":"CUSHMAN"}).explain();
咱们只查问最走侧索引列的时候,索引是失效的
然而如果咱们查问不退出最左侧索引列
COPYdb.zips.find({"state":"NY"}).explain();
咱们发现索引未失效,走了全表扫描
天文索引
天文索引蕴含两种天文类型,如果须要计算的天文数据表示为相似于地球的球形外表上的坐标,则能够应用 2dsphere 索引。
通常能够依照坐标轴、经度、纬度的形式把地位数据存储为 GeoJSON 对象。GeoJSON 的坐标参考系应用的是 wgs84 数据。如果须要计算间隔(在一个欧几里得立体上),通常能够依照失常坐标对的模式存储地位数据,可应用 2d 索引。
创立立体天文索引
如果查找的中央是小范畴的能够应用立体索引
COPYdb.zips.createIndex({"loc":"2d"})
创立球面天文索引
如果是大范畴的,须要思考地球弧度的状况下如果应用平面坐标可能不精确,就须要应用球面索引
COPYdb.zips.createIndex({"loc":"2dsphere"})
罕用索引属性
惟一索引
惟一索引 (unique indexes) 用于为 collection 增加惟一束缚,即强制要求 collection 中的索引字段没有反复值。增加惟一索引的语法:
COPYdb.zips.createIndex({"_id":1,"city":1},{unique:true,name:"id_union_index"})
这样咱们就创立了一个依据 ID 以及 city 的惟一索引
部分索引
部分索引 (Partial Indexes) 顾名思义,只对 collection 的一部分增加索引。创立索引的时候,依据过滤条件判断是否对 document 增加索引,对于没有增加索引的文档查找时采纳的全表扫描,对增加了索引的文档查找时应用索引。
创立索引
COPYdb.zips.createIndex(
{pop:1},
{
partialFilterExpression:
{
pop:
{$gt: 10000}
}
}
)
这样就创立了部分索引
查看执行打算
依据索引个性,咱们晓得,只有查找的人数大于 10000,才会走索引
COPYdb.zips.find({"pop":9999}).explain()
咱们看到,查问 10000 以内的数据不走索引
如果查找的条件大于 10000 就会走索引
COPYdb.zips.find({"pop":99999}).explain()
执行打算
MongoDB 中的
explain()
函数能够帮忙咱们查看查问相干的信息,这有助于咱们疾速查找到搜寻瓶颈进而解决它,本文咱们就来看看explain()
的一些用法及其查问后果的含意。整体来说,
explain()
的用法和sort()
、limit()
用法差不多,不同的是explain()
必须放在最初面。
根本用法
先来看一个根本用法:
COPYdb.zips.find({"pop":99999}).explain()
间接跟在
find()
函数前面,示意查看find()
函数的执行打算,后果如下:
COPY{
"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" : "localhost",
"port" : 27017,
"version" : "4.4.5",
"gitVersion" : "ff5cb77101b052fa02da43b8538093486cf9b3f7"
},
"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
是默认参数,增加 queryPlanner 参数的查问后果就是咱们上文看到的查问后果,so,这里不再赘述。
executionStats
会返回最佳执行打算的一些统计信息,如下:
COPYdb.zips.find({"pop":99999}).explain("executionStats")
咱们发现减少了一个
executionStats
的字段列的信息
COPY{
"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" : "localhost",
"port" : 27017,
"version" : "4.4.5",
"gitVersion" : "ff5cb77101b052fa02da43b8538093486cf9b3f7"
},
"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。
所以优化步骤个别就是:
- 用慢查问日志(system.profile)找到超过 200ms 的语句
- 而后再通过.explain()解析影响行数,剖析为什么超过 200ms
- 决定是不是须要增加索引
开启慢查问
Profiling 级别阐明
COPY0:敞开,不收集任何数据。1:收集慢查问数据,默认是 100 毫秒。2:收集所有数据
针对数据库设置
登录须要开启慢查问的数据库
COPYuse zips-db
查看慢查问状态
COPYdb.getProfilingStatus()
设置慢查问级别
COPYdb.setProfilingLevel(2)
如果不须要收集所有慢日志,只须要收集小于 100ms 的慢日志能够应用如下命令
COPYdb.setProfilingLevel(1,200)
留神:
- 以上要操作要是在 test 汇合上面的话,只对该汇合里的操作无效,要是须要对整个实例无效,则须要在所有的汇合下设置或则在开启的时候开启参数
- 每次设置之后返回给你的后果是批改之前的状态(包含级别、工夫参数)。
全局设置
在 mongoDB 启动的时候退出如下参数
COPYmongod --profile=1 --slowms=200
或则在配置文件里增加 2 行:
COPYprofile = 1
slowms = 200
这样就能够针对所有数据库进行监控慢日志了
敞开 Profiling
应用如下命令能够敞开慢日志
COPYdb.setProfilingLevel(0)
Profile 效率
Profiling 性能必定是会影响效率的,然而不太重大,起因是他应用的是 system.profile 来记录,而 system.profile 是一个 capped collection,这种 collection 在操作上有一些限度和特点,然而效率更高。
慢查问剖析
通过 db.system.profile.find() 查看以后所有的慢查问日志
COPYdb.system.profile.find()
参数含意
COPY{
"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 值比拟大,那么就须要作优化。
- 如果 nscanned 数很大,或者靠近记录总数(文档数),那么可能没有用到索引查问,而是全表扫描。
- 如果 nscanned 值高于 nreturned 的值,阐明数据库为了找到指标文档扫描了很多文档。这时能够思考创立索引来提高效率。
system.profile 补充
‘type’的返回参数阐明
COPYCOLLSCAN #全表扫描
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 的返回
对于一般查问,咱们最心愿看到的组合有这些
COPYFetch+IDHACK
Fetch+ixscan
Limit+(Fetch+ixscan)PROJECTION+ixscan
SHARDING_FILTER+ixscan
等
不心愿看到蕴含如下的 type
COPYCOLLSCAN(全表扫),SORT(应用 sort 然而无 index),不合理的 SKIP,SUBPLA(未用到 index 的 $or)
最初说一句(求关注,别白嫖我)
如果这篇文章对您有所帮忙,或者有所启发的话,帮忙扫描下发二维码关注一下,您的反对是我保持写作最大的能源。
求一键三连:点赞、转发、在看。
本文由
传智教育博学谷狂野架构师
教研团队公布。如果本文对您有帮忙,欢送
关注
和点赞
;如果您有任何倡议也可留言评论
或私信
,您的反对是我保持创作的能源。转载请注明出处!