对比mongodb查询的执行计划,说一说组合索引的优化方案(下)

一、接着上文

上文已说到totalIds在or查询条件中,所以不会匹配到索引。

本文我们试着调整下查询条件,观察调整后,特别是totalIds字段,将匹配哪个索引。(观察的依据仍是查询计划的executionStats)

  • 把totalIds从or查询条件中提取出来
// 调整前
"$or": [
  { "auth": 1 },
  { "totalIds": { "$in": [10001] } }
]

// 调整后
{ "totalIds": { "$in": [10001] } }

实际业务调整,让请求客户端,选择两者中的任意一个条件。这样,就需要写or条件了。

至于auth查询条件,想要查询变快,可以创建索引auth_1_createdOn_0,另外缩短查询的时间范围。(我们近一年auth=1的数据量不过2000条)

本文的主要内容是针对totalIds字段如何匹配索引,以及使用哪个组合索引。

二、新的执行计划

执行的步骤是(由内往外):IXSCAN -> FETCH -> OR -> FETCH -> SORT_KEY_GENERATOR -> SORT -> PROJECTION

可以看到,查询耗时提高了至少百倍,只需要37毫秒,检索的索引数量由十几万减少到几百。

'executionStats': {
   'nReturned': 4,
   'executionTimeMillis': 37,
   'totalKeysExamined': 486,
   'totalDocsExamined': 486,
   'executionStages': {
       'stage': "SINGLE_SHARD",
       'nReturned': 4,
       'executionTimeMillis': 37,
       'totalKeysExamined': 486,
       'totalDocsExamined': 486,
       'totalChildMillis': NumberLong("37"),
       'shards': [
           {
               'shardName': "d-bp1cef3c8241a8a4",
               'executionSuccess': true,
               'executionStages': {
                   'stage': "PROJECTION",
                   'nReturned': 4,
                   'executionTimeMillisEstimate': 1,
                   'works': 495,
                   'advanced': 4,
                   'needTime': 489,
                   'needYield': 0,
                   'saveState': 34,
                   'restoreState': 34,
                   'isEOF': 1,
                   'invalidates': 0,
                   'transformBy': {
                       '$sortKey': {
                           '$meta': "sortKey"
                       }
                   },
                   'inputStage': {
                       'stage': "SORT",
                       'nReturned': 4,
                       'executionTimeMillisEstimate': 1,
                       // 略
                       'inputStage': {
                           'stage': "SORT_KEY_GENERATOR",
                           'nReturned': 4,
                           'executionTimeMillisEstimate': 1,
                           // 略
                           'inputStage': {
                               'stage': "FETCH",
                               'filter': {
                                   // 略
                               },
                               'nReturned': 4,
                               'executionTimeMillisEstimate': 1,
                               // 略
                               'inputStage': {
                                   'stage': "OR",
                                   'nReturned': 486,
                                   'executionTimeMillisEstimate': 0,
                                   // 略
                                   'inputStages': [
                                       {
                                           'stage': "FETCH",
                                           'filter': {
                                               'recycle': {
                                                   '$eq': null
                                               }
                                           },
                                           'nReturned': 0,
                                           'executionTimeMillisEstimate': 0,
                                           // 略
                                           'inputStage': {
                                               'stage': "IXSCAN",
                                               'nReturned': 0,
                                               'executionTimeMillisEstimate': 0,
                                               // 略
                                           }
                                       },
                                       {
                                           'stage': "IXSCAN",
                                           'nReturned': 486,
                                           'executionTimeMillisEstimate': 0,
                                           // 略
                                       }
                                   ]
                               }
                           }
                       }
                   }
               }
           }
       ]
   }
},
  • IXSCAN

查询语句匹配到了索引totalIds_1_isDelete_1_recycle_1_creatorName_1,其中 ‘isMultiKey’: true, 因为totalIds是一个数组。

'stage': "IXSCAN",
'nReturned': 486,
'executionTimeMillisEstimate': 0,
'works': 487,
'advanced': 486,
'needTime': 0,
'needYield': 0,
'saveState': 34,
'restoreState': 34,
'isEOF': 1,
'invalidates': 0,
'keyPattern': {
    'totalIds': 1.0,
    'isDelete': 1.0,
    'recycle': 1.0,
    'creatorName': 1.0
},
'indexName': "totalIds_1_isDelete_1_recycle_1_creatorName_1",
'isMultiKey': true,
'multiKeyPaths': {
    'totalIds': [
        "totalIds"
    ],
    'isDelete': [
    ],
    'recycle': [
    ],
    'creatorName': [
    ]
},
'isUnique': false,
'isSparse': false,
'isPartial': false,
'indexVersion': 2,
'direction': "forward",
'indexBounds': {
    'totalIds': [
        "[10001, 10001]"
    ],
    'isDelete': [
        "[false, false]"
    ],
    'recycle': [
        "[0, 0]"
    ],
    'creatorName': [
        "[MinKey, MaxKey]"
    ]
},
'keysExamined': 486,
'seeks': 1,
'dupsTested': 486,
'dupsDropped': 0,
'seenInvalidated': 0

OR

“第一个”FETCH和IXSCAN两个stage之间是OR关系。这里的IXSCAN和上一个stage相同。

这里说它是“第一个”FETCH,因为外层还有一个FETCH。

本阶段主要是针对recycle查询条件,这也提醒我们在编程的时候,对字段不要赋空,要赋一个默认状态的值。

就拿本例的recycle字段为例:

  • 保存记录的时候,recycle=0
  • 放入回收站的时候,recycle=1
  • 从回收站还原的时候,recycle=0

如此,recycle字段的值要么是0,要么是1。

而查询条件也就可以优化,不存在or查询了。

// 修改前
{"$or":[{"recycle":null},{"recycle":0}]}

// 修改后
{"recycle":0}

在这里插入图片描述

“第二个”FETCH

和前文不同,根据创建时间区间检索,由IXSCAN阶段改为FETCH阶段了。

'stage': "FETCH",
'filter': {
    '$and': [
        {
            'createdOn': {
                '$lte': ISODate("2024-04-29T00:00:00.000Z")
            }
        },
        {
            'createdOn': {
                '$gte': ISODate("2023-04-28T00:00:00.000Z")
            }
        },
        {
            'classroomName': {
                '$regex': ".*大口加小口.*"
            }
        }
    ]
},

SORT

SORT排序是在内存中进行排序,所以它的速度非常快。

在这里插入图片描述

三、组合索引的差异

前文,我们说了,包含totalIds字段的组合索引有两个:

  • totalIds_1_isDelete_1_recycle_1_creatorName_1
  • totalIds_1_createdOn_2

mongodb匹配的是前者,而非后者。(个人认为,Mongo应该是根据组合索引匹配的字段数量来,前者匹配了三个字段,而后者是两个字段)

这也给我们建组合索引提了个醒,不要像我们这样,一通下来,期望匹配的索引(totalIds_1_createdOn_2)反而落榜了。

如果你想要指定使用索引totalIds_1_createdOn_2,在末尾指定hint(“totalIds_1_createdOn_2”)
见下所示:
在这里插入图片描述

当然,对于本查询来说,只要查询条件totalIds匹配了索引,由于其区分度非常高,最后的查询效率都很高。(查询只需几十毫秒)

下面说一说,当指定使用索引totalIds_1_createdOn_2时,执行计划变成怎么样了。

执行步骤为(由内往外):IXSCAN --> FETCH --> SORT_KEY_GENERATOR --> SORT --> PROJECTION

"winningPlan" : {
	 "stage" : "PROJECTION",
	 "transformBy" : {
	         "$sortKey" : {
	                 "$meta" : "sortKey"
	         }
	 },
	 "inputStage" : {
	         "stage" : "SORT",
	         "sortPattern" : {
	                 "createdOn" : -1
	         },
	         "limitAmount" : 20,
	         "inputStage" : {
	                 "stage" : "SORT_KEY_GENERATOR",
	                 "inputStage" : {
	                         "stage" : "FETCH",
	                         "filter" : {
	                               // 略
	                         },
	                         "inputStage" : {
	                                 "stage" : "IXSCAN",
	                                 "keyPattern" : {
	                                         "totalIds" : 1,
	                                         "createdOn" : 1
	                                 },
	                                 "indexName" : "totalIds_1_createdOn_2",
	                                 "isMultiKey" : true,
	                                 "multiKeyPaths" : {
	                                         "totalIds" : [
	                                                 "totalIds"
	                                         ],
	                                         "createdOn" : [ ]
	                                 },
	                                 "isUnique" : false,
	                                 "isSparse" : false,
	                                 "isPartial" : false,
	                                 "indexVersion" : 2,
	                                 "direction" : "forward",
	                                 "indexBounds" : {
	                                         "totalIds" : [
	                                                 "[MinKey, MaxKey]"
	                                         ],
	                                         "createdOn" : [
	                                                 "[MinKey, MaxKey]"
	                                         ]
	                                 }
	                         }
	                 }
	         }
	 }
	}

最内层的索引数据检索,使用了我们指定的索引totalIds_1_createdOn_2。

四、总结

通过两篇文章,我们举例一个集合的多个组合索引,对比分析其执行计划。
因为只有执行计划,才是判定你的查询语句最后是否使用了索引,以及哪个索引。

最后,当一个字段被包含在多个组合索引的时候,更加要小心了,因为我们程序是不会指定使用哪个索引。

相关推荐

  1. MongoDB】--MongoDB组合索引

    2024-05-01 19:48:03       31 阅读
  2. 展开:Android之常用延时执行策略

    2024-05-01 19:48:03       38 阅读

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-05-01 19:48:03       94 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-05-01 19:48:03       100 阅读
  3. 在Django里面运行非项目文件

    2024-05-01 19:48:03       82 阅读
  4. Python语言-面向对象

    2024-05-01 19:48:03       91 阅读

热门阅读

  1. 自动化测试——selenium

    2024-05-01 19:48:03       33 阅读
  2. Docker Error log

    2024-05-01 19:48:03       33 阅读
  3. 每天学习一个Linux命令之dd

    2024-05-01 19:48:03       28 阅读
  4. 使用 Python 和 Keras 实现卷积神经网络

    2024-05-01 19:48:03       31 阅读
  5. 三维装箱问题要点和难点实际应用和案例参考

    2024-05-01 19:48:03       34 阅读
  6. C/C++逻辑与运算与汇编指令的关系

    2024-05-01 19:48:03       30 阅读
  7. Tomcat的请求连接配置

    2024-05-01 19:48:03       32 阅读
  8. 2024年五一联赛数学建模A题思路+代码+论文+结果

    2024-05-01 19:48:03       29 阅读
  9. 2013NOIP普及组真题 1. 计数问题

    2024-05-01 19:48:03       28 阅读
  10. 搜索二叉树模拟实现

    2024-05-01 19:48:03       33 阅读
  11. LP38500/2-ADJ系列

    2024-05-01 19:48:03       35 阅读