最近碰到这样的一个需求,一张酒店政策优惠表,我们要根据用户入住和离开的时间,计算一家酒店的最低价政策前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索引实战的更多相关文章

  1. 深入浅出MongoDB应用实战开发

    写在前面的话: 这篇文章会有点长,谨此记录自己昨天一整天看完<深入浅出MongoDB应用实战开发>视频时的笔记.只是在开始,得先抛出一个困扰自己很长时间的问题:“带双引号的和不带双引号的j ...

  2. [DataBase] MongoDB (7) MongoDB 索引

    MongoDB 索引 1. 建立索引 唯一索引db.passport.ensureIndex( {"loginname": 1}, {"unique": tru ...

  3. MongoDB索引介绍

    MongoDB中的索引其实类似于关系型数据库,都是为了提高查询和排序的效率的,并且实现原理也基本一致.由于集合中的键(字段)可以是普通数据类型,也可以是子文档.MongoDB可以在各种类型的键上创建索 ...

  4. MongoDB(索引及C#如何操作MongoDB)(转载)

    MongoDB(索引及C如何操作MongoDB) 索引总概况 db.test.ensureIndex({"username":1})//创建索引 db.test.ensureInd ...

  5. MongoDB索引(一)

    原文地址 一.介绍 我们已经很清楚索引会提高查询效率.如果没有索引,MongoDB必须对全部集合进行扫描,即,扫描集合中每条文档以选择那些符合查询条件的文档.对查询来说如果存在合适的索引,则Mongo ...

  6. MySQL索引实战经验总结

    MySQL索引对数据检索的性能至关重要,盲目的增加索引不仅不能带来性能的提升,反而会消耗更多的额外资源,本篇总结了一些MySQL索引实战经验. 索引是用于快速查找记录的一种数据结构.索引就像是数据库中 ...

  7. MongoDB 索引篇

    MongoDB 索引篇 索引的简介 索引可以加快查询的速度,但是过多的索引或者规范不好的索引也会影响到查询的速度.且添加索引之后的对文档的删除,修改会比以前速度慢.因为在进行修改的时候会对索引进行更新 ...

  8. MongoDB索引的种类与使用

    一:索引的种类 1:_id索引:是绝大多数集合默认建立的索引,对于每个插入的数据,MongoDB都会自动生成一条唯一的_id字段2:单键索引: 1.单键索引是最普通的索引 2.与_id索引不同,单键索 ...

  9. MongoDB索引,性能分析

    索引的限制: 索引名称不能超过128个字符 每个集合不能超过64个索引 复合索引不能超过31列 MongoDB 索引语法 db.collection.createIndex({ <field&g ...

随机推荐

  1. SQL数据清洗

    大家好,我是jacky,很高兴继续跟大家分享<MySQL数据分析实战>,从本节课程开始,我们的课程就会变得越来越实战,也会越来越有意思了: 我们课程的主体叫MySQL数据分析实战,那我们用 ...

  2. 性能测试学习第八天-----linux环境整合篇

  3. 和小哥哥一起刷洛谷(8) 图论之Floyd“算法”

    关于floyd floyd是一种可以计算图中所有端点之间的最短的"算法",其伪代码如下: for(所有起点i) for(所有终点j) 如果i=j: i到j最短路设为0 如果i与j相 ...

  4. postman上传excel,java后台读取excel生成到指定位置进行备份,并且把excel中的数据添加到数据库

    最近要做个前端网页上传excel,数据直接添加到数据库的功能..在此写个读取excel的demo. 首先新建springboot的web项目 导包,读取excel可以用poi也可以用jxl,这里本文用 ...

  5. Kafka(三) —— 集群监控

    任何应用功能再强大.性能再优越,如果没有与之匹配的监控,那么一切都是虚无缥缈的.监控不仅可以为应用提供运行时的数据作为依据参考,还可以迅速定位问题,提供预防及告警等功能,很大程度上增强了整体服务的鲁棒 ...

  6. Thingsboard HTTP连接至服务器

    当布署了Thingsboard服务器后,可以通过在服务器地址后,加入swagger-ui.html来打开API文档

  7. ping 域名

  8. MacBook Pro设置外接显示器竖屏显示 切换主显示器

    切换主显示器设置 有一些使用 Mac 电脑的用户,比如笔记本用户,可能会由于屏幕太小想外接一个更大的显示器,也或是有多显示输出的需求.当 Mac 电脑上有了多个显示器以后,此时便会有主副显示区之分了. ...

  9. Fiddler抓包工具版面认识(一)

    Fiddler是一个蛮好用的抓包工具,可以将网络传输发送与接受的数据包进行截获.重发.编辑.转存等操作.也可以用来检测网络安全.反正好处多多,举之不尽呀!当年学习的时候也蛮费劲,一些蛮实用隐藏的小功能 ...

  10. python的url正则表达式

    网上有很多的正则表达式版本,大部分都不好使,下面这个比较好用: http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F] ...