mongoDB的索引

whynotgonow發表於2018-05-15

1 索引

  • 索引通常能夠極大的提高查詢的效率,如果沒有索引,MongoDB在讀取資料時必須掃描集合中的每個檔案並選取那些符合查詢條件的記錄。
  • 這種掃描全集合的查詢效率是非常低的,特別在處理大量的資料時,查詢可以要花費幾十秒甚至幾分鐘,這對網站的效能是非常致命的。
  • 索引是特殊的資料結構,索引儲存在一個易於遍歷讀取的資料集合中,索引是對資料庫表中一列或多列的值進行排序的一種結構

2 建立索引

2.1 建立匿名索引

2.1.1 準備資料

  • users資料庫中的classOne集合中新增 1000000 條文件
> use user
> for (var i = 1; i<= 1000000;i++) {
    users.push({name: 'Lily_' + i, num: i })
}
> db.classOne.insert(users);
/*
BulkWriteResult({
	"writeErrors" : [ ],
	"writeConcernErrors" : [ ],
	"nInserted" : 1000000,
	"nUpserted" : 0,
	"nMatched" : 0,
	"nModified" : 0,
	"nRemoved" : 0,
	"upserted" : [ ]
})*/
複製程式碼

2.1.2 查詢{num: 1000000}文件的過程分析

> db.classOne.find({num: 1000000}).explain(true)
{
	"queryPlanner" : {
		"plannerVersion" : 1,
		"namespace" : "user.classOne",
		"indexFilterSet" : false,
		"parsedQuery" : {...},
		"winningPlan" : {
			"stage" : "COLLSCAN",   // 掃描所有的資料 
			"filter" : {...},
			"direction" : "forward"
		},
		"rejectedPlans" : [ ]
	},
	"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 1,
		"executionTimeMillis" : 365,    // 本次查詢執行時間的毫秒數
		"totalKeysExamined" : 0,
		"totalDocsExamined" : 1000000,
		"executionStages" : {
			...
		},
		"allPlansExecution" : [ ]
	},
	"serverInfo" : {
		...
	},
	"ok" : 1
}
複製程式碼

從這個過程的解析物件中,我們得知查詢{num: 1000000}文件,是通過COLLSCAN方式掃描的,執行的時間為 365ms

2.1.3 建立匿名索引

在經常按照文件的倒序查詢的應用場景中,我們可以通過建立索引來進行查詢,以節約我們的查詢時間。

db.collection.ensureIndex(keys, options)
複製程式碼
  • 建立索引
> db.classOne.ensureIndex({num: 1})
/*
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1, // 新增本次索引之前的索引數
	"numIndexesAfter" : 2,  // 新增本次索引之後的索引數
	"ok" : 1
}
*/
複製程式碼
  • 建立索引後再次查詢
> db.classOne.find({num: 1000000}).explain(true)
/*
{
	"queryPlanner" : {
		...
		"winningPlan" : {
			"stage" : "FETCH",
			"inputStage" : {
				"stage" : "IXSCAN", // 通過掃描索引進行查詢
				"keyPattern" : {
					"num" : 1
				},
				"indexName" : "num_1",  // 如果沒有指定索引名稱的話, 預設格式為 ‘欄位_1’ 或者 ‘欄位_-1’ , 1 代表正序, -1 代表 倒序
				"isMultiKey" : false,
				"multiKeyPaths" : {
					"num" : [ ]
				},
				...
			}
		},
		...
	},
	"executionStats" : {
		"executionSuccess" : true,
		"nReturned" : 1,
		"executionTimeMillis" : 6,  // 本次執行時間的毫秒數為 6ms !!!
		...
	},
	"serverInfo" : {...},
	"ok" : 1
}
*/
複製程式碼

建立索引之前用時 365 ms , 建立索引之後查詢用時需要 6 ms, 用時大大的減少了。

2.2 建立命名索引

如果沒有指定索引名稱的話, 預設格式為 fieldName_1 或者 fieldName_-1,如果想要自定義索引名稱的話,可以在建立的時候指定名稱,如下:

> db.classOne.ensureIndex({name: 1}, {name:' myIndexName'})
/*
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 2,
	"numIndexesAfter" : 3,
	"ok" : 1
}
*/
複製程式碼

2.3 檢視索引

> db.classOne.getIndexes()
/*
[
	{
		"v" : 2,
		"key" : {
			"_id" : 1
		},
		"name" : "_id_",    // 原始的 根據 _id 欄位 生成的索引 
		"ns" : "user.classOne"
	},
	{
		"v" : 2,
		"key" : {
			"num" : 1
		},
		"name" : "num_1",   // 根據 num 欄位 升序 建立的索引 
		"ns" : "user.classOne"
	},
	{
		"v" : 2,
		"key" : {
			"name" : 1
		},
		"name" : " myIndexName",   // 自己命名建立的索引
		"ns" : "user.classOne"
	}
]
*/
複製程式碼

2.4 指定需要使用的索引

> db.classOne.find({num: "500000"}).hint({num:1}).explain(true)
複製程式碼

2.5 刪除索引

db.collectionName.dropIndex(IndexName)  刪除指定的索引(IndexName)

db.collecitonName.dropIndex('*');       刪除所有索引
複製程式碼

2.6 在後臺建立索引

db.collection.ensureIndex(keys, {background: true})
複製程式碼

2.7 建立多鍵索引

mongodb可以自動對陣列進行索引

> db.classOne.insert({hobby:['basketball','football','pingpang']});
> db.classOne.ensureIndex({hobby:1});
> db.classOne.find({hobby:'football'},{hobby:1,_id:0}).explain(true);
複製程式碼

2.8 複合索引

查詢的條件不止一個,需要用複合索引

db.collection.ensureIndex({name:1,num:1});
複製程式碼

2.9 過期索引

在一定的時間後會過期,過期後相應資料資料被刪除,比如session、日誌、快取和臨時檔案

> db.classTwo.insert({time:new Date()});
> db.classTwo.ensureIndex({time:1},{expireAfterSeconds:10});
複製程式碼

note

  • 索引欄位的值必須Date物件,不能是其它型別比如時間戳
  • 刪除時間不精確,每60秒跑一次。刪除也要時間,所以有誤差。

2.10 全文索引

大篇幅的文章中搜尋關鍵詞,MongoDB為我們提供了全文索引

2.10.1 建立全文索引

db.colleciton.ensureIndex({field: 'text'})
複製程式碼

note: text- 建立全文索引,1 - 升序索引,2 - 降序索引

2.10.2 usage

語法

  • $text: 表示要在全文索引中查東西
  • $search: 後邊跟查詢的內容, 預設全部匹配

舉個栗子

  • 1 準備資料
// 原始資料
{ "_id" : ObjectId("5afa93eae82637e49ce12077"), "name" : "Lily", "content" : "I am a girl" }
{ "_id" : ObjectId("5afa93f6e82637e49ce12078"), "name" : "Tom", "content" : "I am a boy" }
{ "_id" : ObjectId("5afa9561e82637e49ce12079"), "name" : "Carl", "content" : "I do not know boy girl" }
複製程式碼
  • 2 建立全文索引 根據content欄位建立一個全文索引
db.classThree.ensureIndex({content: 'text'})
複製程式碼
  • 3 根據索引查詢資料
// 查詢包含 ‘girl’ 的文件
> db.classThree.find({$text: {$search: 'girl'}})
// { "_id" : ObjectId("5afa93eae82637e49ce12077"), "name" : "Lily", "content" : "I am a girl" }


// 查詢包含 ‘boy’ 的文件
> db.classThree.find({$text: {$search: 'boy'}})
// { "_id" : ObjectId("5afa93f6e82637e49ce12078"), "name" : "Tom", "content" : "I am a boy" }


// 查詢包含 ‘girl’ 或者 ‘boy’ 的文件(多次查詢是 與 的關係)
> db.classThree.find({$text: {$search: 'boy girl'}})
/*
{ "_id" : ObjectId("5afa93eae82637e49ce12077"), "name" : "Lily", "content" : "I am a girl" }
{ "_id" : ObjectId("5afa9561e82637e49ce12079"), "name" : "Carl", "content" : "I do not know boy girl" }
{ "_id" : ObjectId("5afa93f6e82637e49ce12078"), "name" : "Tom", "content" : "I am a boy" }
*/


// 查詢僅包含‘girl’ 不包含‘boy’的文件
> db.classThree.find({$text: {$search: 'girl -boy'}})
// { "_id" : ObjectId("5afa93eae82637e49ce12077"), "name" : "Lily", "content" : "I am a girl" }


// 就是要查詢包含 ‘girl boy’ 字元的文件(需要轉義)
> db.classThree.find({$text: {$search: '\"boy girl\"'}})
// { "_id" : ObjectId("5afa9561e82637e49ce12079"), "name" : "Carl", "content" : "I do not know boy girl" }
複製程式碼
  • 4 note

多次查詢,多個關鍵字為或的關係,中間以空格隔開

支援轉義符的,用\斜槓來轉義

3 二維索引

mongodb提供強大的空間索引可以查詢出一定落地的地理座標

3.1 建立 2d 索引

db.collection.ensureIndex({field:'2d'}, options)
複製程式碼

3.2 舉個栗子

  • 1 準備資料
{ "_id" : ObjectId("5afaa078e82637e49ce1207a"), "gis" : [ 1, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207b"), "gis" : [ 1, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207c"), "gis" : [ 1, 3 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207d"), "gis" : [ 2, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207e"), "gis" : [ 2, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207f"), "gis" : [ 2, 3 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12080"), "gis" : [ 3, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12081"), "gis" : [ 3, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12082"), "gis" : [ 3, 3 ] }
複製程式碼

如下圖:

2d索引

  • 2 建立 2d 索引
> db.map.ensureIndex({gis:'2d'})
/*
{
	"createdCollectionAutomatically" : false,
	"numIndexesBefore" : 1,
	"numIndexesAfter" : 2,
	"ok" : 1
}
*/
複製程式碼
  • 3 查詢距離[1,1]最近的四個點
> db.map.find({gis:{$near: [1,1]}}).limit(4)
/*
{ "_id" : ObjectId("5afaa078e82637e49ce1207a"), "gis" : [ 1, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207b"), "gis" : [ 1, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207d"), "gis" : [ 2, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207e"), "gis" : [ 2, 2 ] }
*/
複製程式碼

查詢結果如下圖:

near

  • 4 查詢以點[1,2]和點[3,3]為對角線的正方形中的所有的點
> db.map.find({gis: {$within:{$box: [[1,2],[3,3]]}}})
/*
{ "_id" : ObjectId("5afaa078e82637e49ce1207b"), "gis" : [ 1, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207e"), "gis" : [ 2, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207c"), "gis" : [ 1, 3 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207f"), "gis" : [ 2, 3 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12081"), "gis" : [ 3, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12082"), "gis" : [ 3, 3 ] }
*/
複製程式碼

查詢結果如下圖:

box

  • 5 查出以[2,2]為圓心,以1 為半徑為規則下圓心面積中的點
> db.map.find({gis: {$within:{$center: [[2,2],1]}}})
/*
{ "_id" : ObjectId("5afaa078e82637e49ce1207b"), "gis" : [ 1, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207d"), "gis" : [ 2, 1 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207e"), "gis" : [ 2, 2 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce1207f"), "gis" : [ 2, 3 ] }
{ "_id" : ObjectId("5afaa078e82637e49ce12081"), "gis" : [ 3, 2 ] }
*/
複製程式碼

查詢結果如下圖:

center

note: [1,1]等角落裡的座標與[2,2]的座標距離是√2

4 索引使用的注意事項

  • 1為正序 -1為倒序
  • 索引雖然可以提升查詢效能,但會降低外掛效能,對於插入多查詢少不要創索引
  • 資料量不大時不需要使用索引。效能的提升並不明顯,反而大大增加了記憶體和硬碟的消耗。
  • 查詢資料超過表資料量30%時,不要使用索引欄位查詢
  • 排序工作的時候可以建立索引以提高排序速度
  • 數字索引,要比字串索引快的多

相關文章