关于Mongodb索引实战

标签: mongodb 索引 | 发表时间:2015-05-20 10:30 | 作者:snoopyxdy
出处:http://snoopyxdy.blog.163.com
最近碰到这样的一个需求,一张酒店政策优惠表,我们要根据用户入住和离开的时间,计算一家酒店的最低价政策前10位,数据库表字段如下:
     

'hid':88,     酒店id
'date':20150530,  入住日期整形(不要纠结unix时间戳)
'enable':1,  政策是否启用
'price':100, 政策价格
'name':'abc',  政策名称
'position':'china',  酒店位置
'writeTime':datetime.datetime.now(),        写入时间

我们的查询语句也相对固定,都是这样的:
     

db.getCollection('hotels').find({"hid":88, "date":{"$gte":20150501, "$lte":20150510}, "enable":1}).sort({"price":1}).limit(10)

其中条件分为3个:
1、酒店 id :"hid":88
2、date在某个区间里
3、enable启用为1,表示启用
排序条件是一个:
1、price正序排序

现在我往数据库插入10万条测试数据,插入脚本如下:
     

# -*- coding: utf-8 -*-
import pymongo
import json
import datetime,time
import sys
import copy
import sys, os
from multiprocessing import Process, Value, Array
from hashlib import md5
from random import choice, randint


def getTimestampFromDatetime(d=None):
    if d is None:
        d = datetime.datetime.now()
    return time.mktime(d.timetuple())

def md5Hash(str):
    m = md5()
    m.update(str)
    return m.hexdigest().upper()

def task():
    #10分之一的概率无法使用
    enableList = [1,1,1,1,1,1,1,1,1,0]
    dateList = []
    for i in range(31):
        dateInt = 20150501
        dateList.append(dateInt+i)

    mongoUri = 'mongodb://10.14.40.62:27017/hotel'
    all_data = {
        'hid':0,
        'date':0,
        'enable':0,
        'price':0,
        'name':'abc',
        'position':'china',
        'writeTime':datetime.datetime.now(),
    }
    tableName = 'hotels'
    client = pymongo.MongoClient(mongoUri, max_pool_size=100)
    db = client.hotel

    listData = []
    for i in range(100000):
        all_data['price'] = randint(100, 10000)
        all_data['enable'] = choice(enableList)
        all_data['date'] = choice(dateList)
        all_data['hid'] = randint(1, 100)
        listData.append(copy.copy(all_data))

    db[tableName].insert(listData)


if __name__ == '__main__':
    start = getTimestampFromDatetime()
    task()
    end = getTimestampFromDatetime()
    print('time: {0}s'.format(end-start))


一、不建任何索引查询:
我们执行如下语句,查看语句执行情况:
     

db.getCollection('hotels').find({"hid":88, "date":{"$gte":20150501, "$lte":20150510}, "enable":1}).sort({"price":1}).limit(10).explain()

我们看到结果:
     

"n" : 10,
"nscannedObjects" : 100000,
"nscanned" : 100000,
...
"scanAndOrder" : true,
...
"millis" : 200,

其中 n 表示最终返回的结果,nscannedObjects表示我们扫描了多少数据,scanAndOrder表示我们进行了扫描并排序的操作,这是非常消耗cpu和内存的。

从结果来看,我们对10万条数据进行了全表扫描,最终得出10条结果出来。显然这个方案我们不能接受,时间我们花费了200毫秒,这个速度如果上线应用,肯定是不行的。

二、对hid加上索引
我们很容易就想到,对hid加上索引,这样我们第一个结果hid的搜索就可以快速将酒店的索引返回缩小,于是我们创建酒店 hid 的索引,然后同样执行上述语句。
索引如下:
     
{
    "hid" : 1
}
结果如下:
     
"n" : 10,
"nscannedObjects" : 1024,
"nscanned" : 1024,
...
"scanAndOrder" : true,
...
"millis" : 58ms,
对比上述的结果,我们把200ms的查询通过hid索引一下优化到了58ms,从扫描全表10万条数据,修改为只扫描了1024条数据,同时我们的响应时间也下降到了58ms,我们是否可以再优化一下呢?


三、建立hid和date的联合索引
我们发现查询还有第二个参数,date作为时间范围的,所以我们建立一个联合索引,hid:1, date:1这是否可以更加快一些?索引如下:
     
{
    "hid" : 1,
    "date" : 1
}
结果如下:
     
"n" : 10,
"nscannedObjects" : 326,
"nscanned" : 326,
...
"scanAndOrder" : true,
...
"millis" : 6ms,
经过再次优化,这个查询一下就变成6ms返回,只扫描了326行数据了。但是我们只需要返回10条数据,扫描了300多行数据,是否可以再进行一次优化?

四、建立hid、date、enable的联合索引
我们发现查询条件还有第三个参数 enable,由于enable大约有10分之一的数据是我们不要的,就是未启用的政策,所以我们把enable字段也加到索引中,索引如下:
      

{
    "hid" : 1,
    "date" : 1,
    "enable" : 1
}

执行结果如下:
     
"n" : 10,
"nscannedObjects" : 291,
"nscanned" : 300,
...
"scanAndOrder" : true,
...
"millis" : 5ms,
这里nscanned和nscannedObjects不同,nscanned:300表示从数据库索引条目中搜索了300条数据,nscannedObjects表示在这300条中,出最终的10条记录,扫描了这300条中的291条。

根据上面的结果,我们通过索引又进一步优化了这个查询,但是还不满足,我是否可以再增加sort排序的索引来优化呢?

五、建立hid,date,enable,price联合索引
我们把排序的索引也加到联合索引中,看看还能否再进一步优化这个查询了,建立索引如下:
      

{
    "hid" : 1,
    "date" : 1,
    "enable" : 1,
    "price" : 1
}

同样的执行语句结果如下:
     
"n" : 10,
"nscannedObjects" : 291,
"nscanned" : 300,
...
"scanAndOrder" : true,
...
"millis" : 5ms,
我们发现,无论是 nscannedObjects 还是 nscanned,以及查询时间都没有任何帮助了,和之前一样了,似乎我们的优化已经完成了。

六、建立逆索引试试
因为我们的查询条件有一个date作为区间查询的,而最终我们要得到的是根据price排序的结果,所以我们这样建立索引,看看是否对我们的查询有所帮助:
     

{ "hid" : 1, "price" : 1, "date" : 1, "enable" : 1 }

执行结果如下:
     
"n" : 10,
"nscannedObjects" : 10,
"nscanned" : 37,
...
"scanAndOrder" : false,
...
"millis" : 0ms,
看到结果令人满意,我们把成功的把一个原来200ms的查询优化到0ms了,我们从索引查找到37条记录保存在内存里,同时我们只扫描了其中的10条记录就把结果返回了。同时 scanAndOrder 这个字段也成为了false,表示我们没有做在内存里的扫描和排序操作,将会降低cpu和内存的消耗,我们的优化已经完成了。

不过需要指出一点,如果从写入性能来讲,可以考虑把 "enable" : 1 从索引中拿走,毕竟这个索引并不能很好的帮助我们大量减少筛选的数据。

总结一下:
对于这种查询条件有 $in, $gte 等的区间操作的,并且带有sort排序的程序,合理的索引的建立,如果有条件优化到 scanAndOrder 结果为false,将大大的提升我们的数据库性能和响应时间。


相关 [mongodb 索引] 推荐:

MongoDB 索引

- - 博客园_首页
索引是用来加快查询的,数据库索引与数据的索引类似,有了索引就不需要翻遍整本书,数据库可以直接在索引中查找,. 使得查询速度很快,在索引中找到条目后,就可以直接跳转到目标文档的位置.. 要掌握如何为查询配置最佳索引会有些难度.. MongoDB索引几乎和关系型数据库的索引一样.绝大数优化关系型数据库索引的技巧同样适用于MongoDB..

MongoDB索引内部实现

- Rehtron - NoSQLFan
在数据库优化中,索引优化是非常重要也是每一个数据库使用者必须了解的. MongoDB的索引采用BTree结构,除了其地理位置索引外,数据索引本质上和MySQL 的 BTree索引没有什么差别. 下面是一个接近200页的PPT,对MongoDB读写操作相关的索引操作进行了图文讲解. 如果你对数据库索引的实现原理感兴趣,可以仔细看看.

MongoDB索引实战技巧

- - NoSQLFan
本文内容源自 Kyle Banker 的 MongoDB In Action一书. 主要描述了MongoDB 索引相关的一些基础知识和使用技巧. 虽然MongoDB的索引在存储结构上都是一样的,但是根据不同的应用层需求,还是分成了唯一索引(unique)、稀疏索引(sparse)、多值索引(multikey)等几种类型.

MongoDB中索引的用法

- - 数据库 - ITeye博客
本文是一篇转载文章,作者在对 MongoDB文档进行了细致的阅读后,总结出了MongoDB的各种索引的用法. 原文链接: http://iamcaihuafeng.blog.sohu.com/151638529.html. 索引能提高检索数据的速度,你可以想像成在MySQL中创建索引一样,同样索引也是用B-Tree也实现的.

关于Mongodb索引实战

- - snoopyxdy的博客
最近碰到这样的一个需求,一张酒店政策优惠表,我们要根据用户入住和离开的时间,计算一家酒店的最低价政策前10位,数据库表字段如下:. 'hid':88,     酒店id. 'date':20150530,  入住日期整形(不要纠结unix时间戳). 'enable':1,  政策是否启用. 'price':100, 政策价格.

mongodb索引讲解与性能调优

- - haohtml's blog
mongodb索引规则基本上与传统的关系库一样,大部分优化MySQL/Oracle/SQLite索引的技巧也适用于mongodb. 当查询中用到某些条件时,可以对该键建立索引,以提高查询速度. 如果数据量很多且查询多于更新时,可以用索引提高查询的速度. a)         查询索引:. 查询索引很简单,比如说需要查询mailaccess数据库中的Mail collection上的索引时:.

Dex – MongoDB索引优化工具

- - NoSQLFan
Dex是一个开源的MongoDB 优化工具,它通过对查询日志和当前数据库索引进行分析,向管理员提出高效的索引优化策略. 在监控过程中,dex会通过stderr输出推荐的结果. 我们看到,在输出结果中,有一个shellCommand字段,里面就是添加索引的语句,如果你觉得dex的推荐不错,就可以直接复制这段脚本在 MongoDB上添加索引了.

MongoDB范围查询的索引优化

- - NoSQLFan
我们知道, MongoDB的 索引是B-Tree结构的,和MySQL的索引非常类似. 所以你应该听过这样的建议:创建索引的时候要考虑到sort操作,尽量把sort操作要用到的字段放到你的索引后面. 但是有的情况下,这样做反而会使你的查询性能更低. 比如我们进行下面这样的查询:. 查询条件是 {“country”: “A”},按 carsOwned 字段的正序排序.

[mongodb] java操作mongodb

- - 数据库 - ITeye博客
           //实例化Mongo对象,连接27017端口.                               //连接名为yourdb的数据库,假如数据库不存在的话,mongodb会自动建立. //从Mongodb中获得名为yourColleection的数据集合,如果该数据集合不存在,Mongodb会为其新建立.

【MongoDB】MongoDB之优化器Profiler

- - CSDN博客数据库推荐文章
在mysql数据库中,慢查询日志经常作为优化数据库的依据, mongodb中依然有类似的功能. Mongodb自带的profiler,可以方便地记录所有耗时的操作,以便于调优;. 一、开始profiler功能. 开启profier功能有两种:. 第一种就是直接在启动参数里面进行设置,就在茄冬mongodb时候添加-profile=级别.