1. 程式人生 > >MongoDB的索引與查詢優化

MongoDB的索引與查詢優化

MongoDB的索引的機制與普通資料庫基本相似,主要有如下幾部分:

單欄位索引

MongoDB預設為所有集合建立了一個_id欄位的單欄位索引,該索引唯一,且不能刪除(_id為集合的主鍵)

索引的建立方法:

db.customers.ensureIndex({name:1},{unique:false} )

查詢索引:

db.system.indexes.find()

查詢結果:

{ "v" : 1, "name" : "_id_", "key" : { "_id" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "name_1"
, "key" : { "name" : 1 }, "ns" : "test.customers" }

對有索引的查詢選擇器進行解釋:

> db.customers.find({name:'zhangsan'}).explain()
{
"cursor" : "BtreeCursor name_1",//表示該查詢用到了索引
"isMultiKey" : false,//未使用多鍵複合索引
"n" : 10,//查詢選擇匹配到的記錄數量
"nscannedObjects" : 10,//執行查詢掃描到的文件物件數量
"nscanned" : 10,//掃描到的文件或索引總數
"nscannedObjectsAllPlans"
: 10,//掃描文旦總數在所有查詢計劃中 "nscannedAllPlans" : 10,//在所有查詢計劃中掃描的文件或索條目的總數量 "scanAndOrder" : false,//從遊標取出查詢到的資料時,是否對資料進行排序 "indexOnly" : false,// "nYields" : 0,//產生的讀鎖數 "nChunkSkips" : 0, "millis" : 0,//查詢耗時(ms) "indexBounds" : { "name" : [ [ "zhangsan", "zhangsan" ] ] }, "server" : "raspberrypi:27017" }

對上文含義進行解釋看//以後的部分;

注意:以上部分註釋以後也會用到,同時在分析查詢時會經常用到,最好記下來。

複合索引

複合索引主要是指對多個欄位同時新增索引,故而複合索引支援匹配多個欄位的查詢。
建立複合索引:

db.customers.ensureIndex({id:1,age:1})

查詢索引結果:

> db.system.indexes.find()
{ "v" : 1, "name" : "_id_", "key" : { "_id" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "name_1", "key" : { "name" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "id_1_age_1", "key" : { "id" : 1, "age" : 1 }, "ns" : "test.customers" }

使用索引示例:

> db.customers.find({id:{$lt:5},age:{$gt:12}})
{ "_id" : ObjectId("589835c41c85cb68725f789b"), "id" : 3, "name" : "zhangsan", "age" : 13 }
{ "_id" : ObjectId("589835c41c85cb68725f789c"), "id" : 4, "name" : "zhangsan", "age" : 14 }

解釋執行示例:

> db.customers.find({id:{$lt:5},age:{$gt:12}}).explain()
{
"cursor" : "BtreeCursor id_1_age_1",
"isMultiKey" : false,
"n" : 2,
"nscannedObjects" : 2,
"nscanned" : 4,
"nscannedObjectsAllPlans" : 5,
"nscannedAllPlans" : 9,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"id" : [
[
-1.7976931348623157e+308,
5
]
],
"age" : [
[
12,
1.7976931348623157e+308
]
]
},
"server" : "raspberrypi:27017"
}

陣列的多鍵索引

對一個值為陣列型別的欄位建立索引,則會預設對陣列中的每一個元素都建立索引

> db.customers.ensureIndex({'orders.orders_id':1})

檢視:

> db.system.indexes.find()
{ "v" : 1, "name" : "_id_", "key" : { "_id" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "name_1", "key" : { "name" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "id_1_age_1", "key" : { "id" : 1, "age" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "orders.products.product_name_1", "key" : { "orders.products.product_name" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "orders.orders_id_1", "key" : { "orders.orders_id" : 1 }, "ns" : "test.customers" }

解釋執行:

> db.customers.find({'orders.orders_id':{$gte:0}}).explain()
{
"cursor" : "BtreeCursor orders.orders_id_1",
"isMultiKey" : false,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"orders.orders_id" : [
[
0,
1.7976931348623157e+308
]
]
},
"server" : "raspberrypi:27017"
}

另外一個:

> db.customers.find({'orders.products.product_name':'iphone'})
{ "_id" : ObjectId("58983d0fc55e261327343eab"), "id" : 11, "name" : "lisi", "orders" : [ { "orders_id" : 1, "create_time" : "2017-02-06", "products" : [ { "product_name" : "MiPad", "price" : "$100.00" }, { "product_name" : "iphone", "price" : "$399.00" } ] } ], "mobile" : "13161020110", "address" : { "city" : "beijing", "street" : "taiyanggong" } }
> db.customers.find({'orders.products.product_name':'iphone'}).explain()
{
"cursor" : "BtreeCursor orders.products.product_name_1",
"isMultiKey" : true,
"n" : 1,
"nscannedObjects" : 1,
"nscanned" : 1,
"nscannedObjectsAllPlans" : 1,
"nscannedAllPlans" : 1,
"scanAndOrder" : false,
"indexOnly" : false,
"nYields" : 0,
"nChunkSkips" : 0,
"millis" : 0,
"indexBounds" : {
"orders.products.product_name" : [
[
"iphone",
"iphone"
]
]
},
"server" : "raspberrypi:27017"
}

索引管理

索引的建立我們已經在上文中有過講述,下面總結下索引的建立格式:

db.collection.ensureIndex(keys,options)

其中key是一個document文件,包含需要新增索引的欄位以及索引的排序方向;option可選,控制索引的建立方式;
所有的索引都儲存在集合system.indexes中;
索引的刪除方式為:

db.collection.dropIndex(indexName)

如:刪除customers的name_1索引:

> db.customers.dropIndex('name_1')
{ "nIndexesWas" : 5, "ok" : 1 }
> db.system.indexes.find()
{ "v" : 1, "name" : "_id_", "key" : { "_id" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "id_1_age_1", "key" : { "id" : 1, "age" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "orders.products.product_name_1", "key" : { "orders.products.product_name" : 1 }, "ns" : "test.customers" }
{ "v" : 1, "name" : "orders.orders_id_1", "key" : { "orders.orders_id" : 1 }, "ns" : "test.customers" }

慢查詢監控

1、MongoDB會自動的將查詢語句執行時間超過100ms的輸出到日誌中,其中100ms可以通過mongod的啟動選項 slowms設定,預設100ms
2、還可以通過開啟資料庫的監視功能,預設是關閉的,通過如下命令開啟

db.setProfilingLevel(level,[slowms])

level:監視級別,值為0為關閉,1:只記錄慢日誌,2:記錄所有的操作
監視的結果都儲存在system.profile中。

示例如下:

> db.setProfilingLevel(2)
{ "was" : 0, "slowms" : 100, "ok" : 1 }
> db.customers.find()
{ "_id" : ObjectId("589835c41c85cb68725f7899"), "id" : 1, "name" : "zhangsan", "age" : 11 }
{ "_id" : ObjectId("589835c41c85cb68725f789a"), "id" : 2, "name" : "zhangsan", "age" : 12 }
{ "_id" : ObjectId("589835c41c85cb68725f789b"), "id" : 3, "name" : "zhangsan", "age" : 13 }
{ "_id" : ObjectId("589835c41c85cb68725f789c"), "id" : 4, "name" : "zhangsan", "age" : 14 }
{ "_id" : ObjectId("589835c41c85cb68725f789d"), "id" : 5, "name" : "zhangsan", "age" : 15 }
{ "_id" : ObjectId("589835c41c85cb68725f789e"), "id" : 6, "name" : "zhangsan", "age" : 16 }
{ "_id" : ObjectId("589835c41c85cb68725f789f"), "id" : 7, "name" : "zhangsan", "age" : 17 }
{ "_id" : ObjectId("589835c41c85cb68725f78a0"), "id" : 8, "name" : "zhangsan", "age" : 18 }
{ "_id" : ObjectId("589835c41c85cb68725f78a1"), "id" : 9, "name" : "zhangsan", "age" : 19 }
{ "_id" : ObjectId("589835c41c85cb68725f78a2"), "id" : 10, "name" : "zhangsan", "age" : 20 }
{ "_id" : ObjectId("58983d0fc55e261327343eab"), "id" : 11, "name" : "lisi", "orders" : [ { "orders_id" : 1, "create_time" : "2017-02-06", "products" : [ { "product_name" : "MiPad", "price" : "$100.00" }, { "product_name" : "iphone", "price" : "$399.00" } ] } ], "mobile" : "13161020110", "address" : { "city" : "beijing", "street" : "taiyanggong" } }
> db.system.profile.find()
{ "op" : "query", "ns" : "test.system.indexes", "query" : { "expireAfterSeconds" : { "$exists" : true } }, "ntoreturn" : 0, "ntoskip" : 0, "nscanned" : 4, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(294), "w" : NumberLong(0) }, "timeAcquiringMicros" : { "r" : NumberLong(13), "w" : NumberLong(15) } }, "nreturned" : 0, "responseLength" : 20, "millis" : 0, "ts" : ISODate("2017-02-09T03:45:41.845Z"), "client" : "0.0.0.0", "allUsers" : [ ], "user" : "" }
{ "op" : "query", "ns" : "test.customers", "query" : { }, "ntoreturn" : 0, "ntoskip" : 0, "nscanned" : 11, "keyUpdates" : 0, "numYield" : 0, "lockStats" : { "timeLockedMicros" : { "r" : NumberLong(252), "w" : NumberLong(0) }, "timeAcquiringMicros" : { "r" : NumberLong(19), "w" : NumberLong(16) } }, "nreturned" : 11, "responseLength" : 995, "millis" : 0, "ts" : ISODate("2017-02-09T03:45:49.972Z"), "client" : "127.0.0.1", "allUsers" : [ ], "user" : "" }
>