MongoDB索引機制

mongodb索引

什麼是索引

當咱們查詢數據庫時,若是不創建索引。默認是經過咱們編寫的規則去遍歷數據庫中全部的文檔,最後找出符合條件的文檔。這在數據量不是很大的時候問題不是很大,可是若是數據量很大,查詢有可能花費數秒甚至數分鐘的時間。mongodb

而索引會將數據按照必定的順序進行排序,當咱們查詢的時候經過這個順序就會很快的查詢出來(O(logN)的時間複雜度)數據庫

內部原理 當咱們往數據庫中存儲數據時,經過底層的存儲引擎持久化以後,會記錄文檔的位置信息,經過這個位置信息就能查找到對應的文檔數組

例如:咱們在數據庫中插入如下信息bash

> db.test.find()
{ "_id" : ObjectId("5d47f95c4903b485d29ba952"), "name" : "fqr0", "age" : 0 }
{ "_id" : ObjectId("5d47f95c4903b485d29ba953"), "name" : "fqr1", "age" : 1 }
{ "_id" : ObjectId("5d47f95c4903b485d29ba954"), "name" : "fqr2", "age" : 2 }
複製代碼

數據庫會記錄文檔的位置信息app

位置信息 文檔
位置1 {"name":"far1", "age":1}
位置2 {"name":"far0", "age":0}
位置3 {"name":"far2", "age":2}

這時咱們要查詢find({"age":2})時,會遍歷全部的三個文檔,當數據量很大時查詢會很慢,若是咱們想加快查詢速度,就能夠對age字段加索引dom

db.test.createIndex({"age":1}) // 按照age字段建立升序序列
複製代碼

創建索引後性能

age 位置信息
0 位置2
1 位置1
2 位置3

這樣就不用遍歷全部的文檔去查找符合條件{"age":2}的數據了測試

其實在MongoDB文檔中都有一個_id字段,它就是一個索引,用來經過_id快速的查詢文檔優化

索引的好處ui

  1. 加快搜索的速度
  2. 優化更新和刪除操做,由於這兩個操做都是先查詢出對應的文檔而後在執行更新或者刪除
  3. 加快排序的速度,若是須要對age字段排序,就不須要再去遍歷全部文檔了

MongoDB索引類型

單字段索引 就是隻對單個字段進行索引

db.test.createIndex({"age":1})
複製代碼

1 表示升序,-1表示降序

單字段索引是最經常使用的索引方式,MongoDB默認建立的_id索引就是這種方式

複合段索引 對多個字段進行索引

db.test.createIndex({"age":1, "name":1})
複製代碼

多字段索引的方式是若是文檔的age相同,就經過name字段排序

例如:

age,name 位置信息
0,fqr0 位置2
0,far1 位置1
2,fqr1 位置3

注意:採用這種索引創建方式,不只能知足多個字段的查詢find({"age":0,"name":"fqr1"}),也能夠知足單個字段的查詢find({"age":0})。可是,find({"name":"fqr0"})是利用不了索引的

採用這種方式時,一般選擇不會容易重複的字段做爲第一個條件,這樣性能會更好

多Key索引

若是一個字段爲數組時,對這個字段創建索引就是多key索引,數據庫會爲其中的每一個元素創建索引

db.test.createIndex({"field": 1})
複製代碼

不經常使用的索引

  1. 文本索引,好比經過文章的關鍵詞,在一個博客系統中查詢對應的文章
  2. 哈希索引,按照某個字段的hash值進行索引
  3. 地理位置索引,好比查找附件的美食等

索引優化

MongoDB支持對數據庫的操做進行分析,記錄操做比較慢的動做。一共有三個level

  1. 不記錄慢操做
  2. 將處理時間超過閥值的請求記錄都記錄到system.profile集合
  3. 將全部的請求都記錄到system.profile集合

設置level

> db.setProfilingLevel(1)
{ "was" : 1, "slowms" : 100, "sampleRate" : 1, "ok" : 1 }
複製代碼

查看level

> db.getProfilingLevel()
1
複製代碼

咱們下面測試一下profile

首先在數據庫中寫入大量的數據

for(let i=0;i<1000000;i++){db.test.insertOne({"name":"fqr"+i,"age":parseInt(i * Math.random())}) }
複製代碼

沒有創建索引前查詢都是全表掃描

> db.test.find({"name":"fqr1234"}).explain()
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.test",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "fqr1234"
			}
		},
		"winningPlan" : {
			"stage" : "COLLSCAN",   // 全表掃描
			"filter" : {
				"name" : {
					"$eq" : "fqr1234"
				}
			},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"ok" : 1
}
複製代碼

咱們查看system.profile,發現已經記錄了這條數據,由於查詢時間是1202毫秒,超過了默認值100毫秒

> db.system.profile.find().sort({$natural:-1}).limit(1).pretty()
{
	"op" : "command",
	"ns" : "test.test",
	"command" : {
		"explain" : {
			"find" : "test",
			"filter" : {
				"name" : "fqr1234"
			}
		},
		"verbosity" : "allPlansExecution",
		"$db" : "test"
	},
	"numYield" : 7834,
	"locks" : {
		"Global" : {
			"acquireCount" : {
				"r" : NumberLong(7835)
			}
		},
		"Database" : {
			"acquireCount" : {
				"r" : NumberLong(7835)
			}
		},
		"Collection" : {
			"acquireCount" : {
				"r" : NumberLong(7835)
			}
		}
	},
	"responseLength" : 862,
	"protocol" : "op_msg",
	"millis" : 1202,
	"ts" : ISODate("2019-08-06T02:14:59.455Z"),
	"client" : "127.0.0.1",
	"appName" : "MongoDB Shell",
	"allUsers" : [ ],
	"user" : ""
}
複製代碼

咱們優化查詢速度時,能夠根據system.profile中的記錄來創建相關字段的索引,提高查詢速度

下面咱們創建一個索引

> db.test.createIndex({"name":1})
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
複製代碼

再進行查詢

> db.test.find({"name":"fqr12345"}).explain("allPlansExecution")
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "test.test",
		"indexFilterSet" : false,
		"parsedQuery" : {
			"name" : {
				"$eq" : "fqr12345"
			}
		},
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN",
				"keyPattern" : {
					"name" : 1
				},
				"indexName" : "name_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[\"fqr12345\", \"fqr12345\"]"
					]
				}
			}
		},
		"rejectedPlans" : [ ]
	},
	"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 1,
		"executionTimeMillis" : 4,
		"totalKeysExamined" : 1,
		"totalDocsExamined" : 1,
		"executionStages" : {
			"stage" : "FETCH",
			"nReturned" : 1,
			"executionTimeMillisEstimate" : 0,
			"works" : 2,
			"advanced" : 1,
			"needTime" : 0,
			"needYield" : 0,
			"saveState" : 0,
			"restoreState" : 0,
			"isEOF" : 1,
			"invalidates" : 0,
			"docsExamined" : 1,
			"alreadyHasObj" : 0,
			"inputStage" : {
				"stage" : "IXSCAN",
				"nReturned" : 1,
				"executionTimeMillisEstimate" : 0,
				"works" : 2,
				"advanced" : 1,
				"needTime" : 0,
				"needYield" : 0,
				"saveState" : 0,
				"restoreState" : 0,
				"isEOF" : 1,
				"invalidates" : 0,
				"keyPattern" : {
					"name" : 1
				},
				"indexName" : "name_1",
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"name" : [ ]
				},
				"isUnique" : false,
				"isSparse" : false,
				"isPartial" : false,
				"indexVersion" : 2,
				"direction" : "forward",
				"indexBounds" : {
					"name" : [
						"[\"fqr12345\", \"fqr12345\"]"
					]
				},
				"keysExamined" : 1,
				"seeks" : 1,
				"dupsTested" : 0,
				"dupsDropped" : 0,
				"seenInvalidated" : 0
			}
		},
		"allPlansExecution" : [ ]
	},
	"ok" : 1
}
複製代碼

會發現查詢已經再也不是全表掃描了,而是根據索引查詢,而且查詢速度由原來的1202ms提高到了如今的4ms

經常使用命令

  1. 查看索引

    > db.test.getIndexes()
    [
        {
            "v" : 2,
            "key" : {
                "_id" : 1
            },
            "name" : "_id_",
            "ns" : "test.test"
        },
        {
            "v" : 2,
            "key" : {
                "name" : 1
            },
            "name" : "name_1",
            "ns" : "test.test"
        }
    ]
    複製代碼
  2. 指定須要使用的索引

    > db.test.find({"name":"fqr12345"}).hint({"name":1}).pretty()
    {
        "_id" : ObjectId("5d47f9604903b485d29bd98b"),
        "name" : "fqr12345",
        "age" : 5526
    }
    複製代碼
  3. 刪除索引

> db.test.dropIndex("name_1")
# 刪除全部索引
> db.test.dropIndex("*")
複製代碼

注意事項

  1. 雖然索引能夠提高查詢性能,可是會下降插入性能
  2. 數字索引要比字符串索引快得多
相關文章
相關標籤/搜索