鍍金池/ 問答/數據庫/ mongodb走了索引,依舊是慢查詢,請看執(zhí)行計劃

mongodb走了索引,依舊是慢查詢,請看執(zhí)行計劃

根據索引in查詢, 掃描索引3條 keysExamined 3, 返回文檔數1條 nReturned 1, 耗時 millis 452毫秒, execStats中executionTimeMillisEstimate都是0.
問題1: executionTimeMillisEstimate都是0. 為啥整個查詢卻消耗452毫秒.
問題2: 走了索引,只掃描索引3條,返回文檔數1條,消耗452毫秒,怎么優(yōu)化或者怎么調整它.
問題3: 在其它的執(zhí)行計劃中我看到inputStage.stage.FETCH操作耗時也很高,在200~1000毫秒.FETCH根據索引取下文檔為啥還要這么多時間呢.
求助 @Mongoing中文社區(qū) @bguo

{
  "op": "query",
  "ns": "webDevice",
  "query": {
    "find": "webDevice",
    "filter": {
      "lid": {
        "$in": [
          "40CnwyHkVmnA9kbScMLNLneaxuS4Tcj",
          "140CnwyHkVmnA9kbScMLNLneaxuS4Tcj"
        ]
      }
    },
    "projection": {
      "$sortKey": {
        "$meta": "sortKey"
      }
    },
    "sort": {
      "createTime": -1
    },
    "limit": 1,
    "shardVersion": [
      {
        "$timestamp": {
          "t": 106,
          "i": 0
        }
      },
      {
        "$oid": "59b0039e9b5e66530435be05"
      }
    ]
  },
  "keysExamined": 3,
  "docsExamined": 1,
  "hasSortStage": true,
  "cursorExhausted": true,
  "keyUpdates": 0,
  "writeConflicts": 0,
  "numYield": 0,
  "locks": {
    "Global": {
      "acquireCount": {
        "r": 2
      }
    },
    "Database": {
      "acquireCount": {
        "r": 1
      }
    },
    "Collection": {
      "acquireCount": {
        "r": 1
      }
    }
  },
  "nreturned": 1,
  "responseLength": 1267,
  "protocol": "op_command",
  "millis": 452,
  "execStats": {
    "stage": "CACHED_PLAN",
    "nReturned": 1,
    "executionTimeMillisEstimate": 0,
    "works": 2,
    "advanced": 1,
    "needTime": 0,
    "needYield": 0,
    "saveState": 0,
    "restoreState": 0,
    "isEOF": 1,
    "invalidates": 0,
    "inputStage": {
      "stage": "PROJECTION",
      "nReturned": 1,
      "executionTimeMillisEstimate": 0,
      "works": 5,
      "advanced": 1,
      "needTime": 4,
      "needYield": 0,
      "saveState": 0,
      "restoreState": 0,
      "isEOF": 1,
      "invalidates": 0,
      "transformBy": {
        "$sortKey": {
          "$meta": "sortKey"
        }
      },
      "inputStage": {
        "stage": "SORT",
        "nReturned": 1,
        "executionTimeMillisEstimate": 0,
        "works": 5,
        "advanced": 1,
        "needTime": 4,
        "needYield": 0,
        "saveState": 0,
        "restoreState": 0,
        "isEOF": 1,
        "invalidates": 0,
        "sortPattern": {
          "createTime": -1
        },
        "memUsage": 1031,
        "memLimit": 33554432,
        "limitAmount": 1,
        "inputStage": {
          "stage": "SORT_KEY_GENERATOR",
          "nReturned": 0,
          "executionTimeMillisEstimate": 0,
          "works": 4,
          "advanced": 0,
          "needTime": 2,
          "needYield": 0,
          "saveState": 0,
          "restoreState": 0,
          "isEOF": 1,
          "invalidates": 0,
          "inputStage": {
            "stage": "SHARDING_FILTER",
            "nReturned": 1,
            "executionTimeMillisEstimate": 0,
            "works": 3,
            "advanced": 1,
            "needTime": 1,
            "needYield": 0,
            "saveState": 0,
            "restoreState": 0,
            "isEOF": 1,
            "invalidates": 0,
            "chunkSkips": 0,
            "inputStage": {
              "stage": "FETCH",
              "nReturned": 1,
              "executionTimeMillisEstimate": 0,
              "works": 3,
              "advanced": 1,
              "needTime": 1,
              "needYield": 0,
              "saveState": 0,
              "restoreState": 0,
              "isEOF": 1,
              "invalidates": 0,
              "docsExamined": 1,
              "alreadyHasObj": 0,
              "inputStage": {
                "stage": "IXSCAN",
                "nReturned": 1,
                "executionTimeMillisEstimate": 0,
                "works": 3,
                "advanced": 1,
                "needTime": 1,
                "needYield": 0,
                "saveState": 0,
                "restoreState": 0,
                "isEOF": 1,
                "invalidates": 0,
                "keyPattern": {
                  "lid": -1
                },
                "indexName": "lid_-1",
                "isMultiKey": false,
                "isUnique": false,
                "isSparse": false,
                "isPartial": false,
                "indexVersion": 1,
                "direction": "forward",
                "indexBounds": {
                  "lid": [
                    "[\"40CnwyHkVmnA9kbScMLNLneaxuS4Tcj\", \"40CnwyHkVmnA9kbScMLNLneaxuS4Tcj\"]",
                    "[\"140CnwyHkVmnA9kbScMLNLneaxuS4Tcj\", \"140CnwyHkVmnA9kbScMLNLneaxuS4Tcj\"]"
                  ]
                },
                "keysExamined": 3,
                "dupsTested": 0,
                "dupsDropped": 0,
                "seenInvalidated": 0
              }
            }
          }
        }
      }
    }
  },
  "ts": {
    "$date": 1514285478923
  },
  "client": "10.105.122.126",
  "allUsers": [
    {
      "user": "__system",
      "db": "local"
    }
  ],
  "user": "__system@local",
  "_id": "c044e94198e245f3e61b39d230feb393-20171226105118923-200109374"
}

補充說明

我補充一下我的環(huán)境: 機器是8核16內存. 機器上部署有5個mongodb實例(在docker容器里面),1個mongos,1個config,3個shard(1個主,1個從,1個arbiter).
以下是docker的內存使用情況.

cpu使用情況 內存使用情況
arbiter實例 1.83% 80.18MiB / 15.51GiB
shard2從 3.09% 5.306GiB / 15.51GiB
config 1.81% 1.449GiB / 15.51GiB
shard3主 2.56% 5.025GiB / 15.51GiB
mongos 0.37% 188.3MiB / 15.51GiB

其中config,shard主,shard從.3個實例都設置了CacheSizeGB為3.
目前從資源使用情況來看,CPU使用率都很低,內存config雖然限制了3GB,但整個docker容器只用了1.5GB的內存.

  1. 疑問1: 按照你說的方式調小cacheSizeGB,應該怎么調整比較合理
  2. 疑問2: config只用了1.5GB內存(限制了CacheSizeGB3GB).可以說明索引都加載到了內存中嗎?
  3. 疑問3: mongodb remove數據后,查詢卻越來越慢是什么情況?
回答
編輯回答
尐懶貓

下次記得把原始查詢也發(fā)出來,我們看著更方便些。從執(zhí)行計劃來反推,查詢大約是

db.webDevice.find({
    lid: {
        $in: [
            "40CnwyHkVmnA9kbScMLNLneaxuS4Tcj",
            "140CnwyHkVmnA9kbScMLNLneaxuS4Tcj"
        ]
    }
}).sort({createTime: -1}).limit(1);

因為命中索引,這個查詢獲取數據的速度實際上比較快,你也提到在一段時間內第二次查詢就快了,這是一個很明顯的特點,代表內存不足。MongoDB和其他數據庫一樣,都會使用空閑內存緩沖索引和數據,當內存不足時就會使用LRU算法清理舊數據換入新數據再繼續(xù)查詢。因為這個過程涉及到磁盤數據交換,速度會大大降低。發(fā)生這種情況有一個特點,就是一段時間內第二次執(zhí)行時速度就快了(因為數據已經在內存中)。但是如果過一段時間再執(zhí)行,速度又會變慢(因為又被換出內存了)。所以你的情況實際上就是受限于硬件。
既然如此最直接的解決辦法就是:

  1. 使用更快的硬盤;
  2. 使用更大的內存;

如果不能從硬件方面解決,有一點可以嘗試就是用CPU換內存。做法是盡可能調小cacheSizeGB(是的沒錯,是調?。???臻e出來的內存操作系統(tǒng)會用來緩沖磁盤數據,而磁盤數據是經過壓縮的,體積更小,因此可以緩沖更多數據到內存中。但作為交換,在使用這些數據時需要經過CPU再進行一次解壓從而額外消耗CPU。即使這樣,效果也比從磁盤讀取要好很多。整個過程是自動進行的,你需要做的只是調小cacheSizeGB
這種做法可以在一定程度上緩解內存不足的問題,但不是萬能的:

  • 首先它會增加CPU消耗,如果你的系統(tǒng)本身已經沒有剩余的CPU資源,這種做法就不合適;
  • 其次受制于壓縮率,這樣做之后內存能容納的數據并不會比以前多很多,所以并不是萬能的;

補充回答

基于你的新的疑問,以下幾點補充:

疑問1: 按照你說的方式調小cacheSizeGB,應該怎么調整比較合理

我在上面有提到,這樣做的效果有限,是受制于壓縮率的限制。所以多容納的數據實際就是壓縮的數據。比如1G的內存能放多少數據?

  • 如果不壓縮(壓縮率100%),1G內存能放1GB數據;
  • 如果壓縮率90%,1G內存可以放10/9GB~=1.11數據;
  • 如果壓縮率80%,1G內存可以放10/8 = 1.25GB數據;
  • 以此類推……

所以調到多少,其實要看你想往內存里放多少數據,而這往往是一個不確定的數值。這里會有另外一個概念叫做工作集(working set),就是你經常用到的那些數據。比如你的數據庫共有100GB數據,但是你經常用到的部分只有10GB,那么你的內存只要能裝下10GB數據,應用在大部分時候就可以非??欤O碌那闆r忽略就好了。
基于上面這些分析,你應該可以算一下,你的工作集有多大,數據壓縮率有多大,那么需要把cacheSizeGB調到多小才能容納下工作集(又或者調到多小都不可能容納下工作集)。你的情況是內存不足CPU空閑,所以如果懶得算,直接把cacheSizeGB調到最小值就好了。

疑問2: config只用了1.5GB內存(限制了CacheSizeGB3GB).可以說明索引都加載到了內存中嗎?

這說明config數據庫(元數據)的索引和數據都加載到內存中了。注意數據的索引肯定是在shard中,與config無關。而且大頭是在shard上面。
順便提一下,如果不是實驗目的,根本沒必要分這么多片。因為在一臺機器上分再多片,硬件資源也只有這么多,對于性能沒有什么意義,反而還會有額外的傳輸開銷。

疑問3: mongodb remove數據后,查詢卻越來越慢是什么情況?

remove之后跟查詢沒有本質上的聯(lián)系,可能只是湊巧發(fā)生在一起。如果你有足夠的證據覺得這兩者確實有聯(lián)系,請另開問題描述清楚問題的上下文以及你發(fā)現(xiàn)的情況,必要的時候也可以求助于MongoDB JIRA。如果一定要做一個無根據的猜測,我覺得可能是remove時導致熱數據被換出內存(注意remove也需要先找到滿足條件的數據然后才能刪除),引起后面的查詢需要重新從磁盤上加載數據造成的。

2017年7月10日 15:55