最近项目在做网站用户数据新访客统计,数据存储在MongoDB中,统计的数据其实也并不是很大,1000W上下,但是公司只配给我4G内存的电脑,让我程序跑起来气喘吁吁...很是疲惫不堪。

最常见的问题莫过于查询MongoDB内存溢出,没办法只能分页查询。这种思想大家可能都会想到,但是如何分页,确实多有门道!

网上用的最多的,也是最常见的分页采用的是skip+limit这种组合方式,这种方式对付小数据倒也可以,但是对付上几百上千万的大数据,却只能望而兴叹...

经过网上各种查找资料,寻师问道的,发现了一种速度足以把skip+limit组合分页甩出几条街的方法。

思路: 条件查询+排序+限制返回记录。边查询,边排序,排序之后,抽取第一次分页中的最后一条记录,作为第二次分页的条件,进行条件查询,以此类推....

先上代码:

 /**
* 小于指定日期的所有根据UUID分组的访问记录
* @param 指定日期
* @return 所有访问记录的MAP
*/
public static Multimap<String, Map<String, String>> getOldVisitors(String date){ //每次查询的记录数
int pagesize = 100000; //mongodb中的"_id"
String objectId = ""; //方法的返回值类型,此处用的google guava
Multimap<String, Map<String, String>> mapless = null; //查询的条件
BasicDBObject queryless = new BasicDBObject(),fields = new BasicDBObject(),field = new BasicDBObject(); //初始化返回的mongodb集合操作对象,大家可以写个数据连接池
dbCol = init(); //查询指定字段,字段越少,查询越快,当然都是一些不必要字段
field.put("uuid",1); fields.put("uuid", 1); fields.put("initTime", 1); //小于指定日期的条件
String conditionless = TimeCond.getTimeCondless(date); queryless.put("$where", conditionless); DBCursor cursorless = dbCol.find(queryless,field); //MongoDB在小于指定日期条件下,集合总大小
int countless = cursorless.count(); //查询遍历的次数 circleCountless+1
int circleCountless = countless/pagesize; //取模,这是最后一次循环遍历的次数
int modless = countless%pagesize; //开始遍历查询
for (int i = 1; i <=circleCountless+1; i++) { //文档对象
DBObject obj = null; //将游标中返回的结果记录到list集合中,为什么放到list集合中?这是为后面guava 分组做准备
List<Map<String, String>> listOfMaps = new ArrayList(); //如果条件不为空,则加上此条件,构成多条件查询,这一步是分页的关键
if (!"".equals(objectId)) { //我们通过文档对象obj.get("_id")返回的是不带ObjectId(),所以要求此步骤
ObjectId id = new ObjectId(objectId); queryless.append("_id", new BasicDBObject("$gt",id)); } if (i<circleCountless+1) { cursorless = dbCol.find(queryless,fields).sort(new BasicDBObject("_id", 1)).limit(pagesize); }else if(i==circleCountless+1){//最后一次循环 cursorless = dbCol.find(queryless,fields).limit(modless);
} //将游标中返回的结果记录到list集合中,为什么放到list集合中?这是为后面guava 分组做准备
while (cursorless.hasNext()) { obj = cursorless.next(); listOfMaps.add((Map<String, String>) obj); }
//获取一次分页中最后一条记录的"_id",然后作为条件传入到下一个循环中
if (null!=obj) { objectId = obj.get("_id").toString(); }
//第一次分组,根据uuid分组,分组除今天之外的历史数据
mapless = Multimaps.index(
listOfMaps,new Function<Map<String, String>, String>() {
public String apply(final Map<String, String> from) { return from.get("uuid");
}
}); } return mapless;
}

这里为什么要用"_id"这个字段作为分页的条件?其实,我也用过其他字段,比如时间字段,时间字符串也是可以比大小的,但它的效率远不如"_id"高。

关于MongoDB中的"_id",以前一直忽略它的作用,直接结果是让我耗了很多时间和精力,绕了大半圈,又回到了原点,有一种众里寻他千百度,蓦然回首,那人却在灯火阑珊处的感觉...

    MongoDB ObjectId

“4e7020cb7cac81af7136236b”这个24位的字符串,虽然看起来很长,也很难理解,但实际上它是由一组十六进制的字符构成,每个字节两位的十六进制数字,总共用了12字节的存储空间。相比MYSQLint类型的4个字节,MongoDB确实多出了很多字节。不过按照现在的存储设备,多出来的字节应该不会成为什么瓶颈。不过MongoDB的这种设计,体现着空间换时间的思想。官网中对ObjectId的规范,如图所示:

1)Time

时间戳。将刚才生成的objectid的前4位进行提取“4e7020cb”,然后按照十六进制转为十进制,变为“1315971275”,这个数字就是一个时间戳。通过时间戳的转换,就成了易看清的时间格式。

2)Machine

机器。接下来的三个字节就是“7cac81”,这三个字节是所在主机的唯一标识符,一般是机器主机名的散列值,这样就确保了不同主机生成不同的机器hash值,确保在分布式中不造成冲突,这也就是在同一台机器生成的objectId中间的字符串都是一模一样的原因。

    3)PID

进程ID。上面的Machine是为了确保在不同机器产生的objectId不冲突,而pid就是为了在同一台机器不同的mongodb进程产生了objectId不冲突,接下来的“af71”两位就是产生objectId的进程标识符。

4)INC

自增计数器。前面的九个字节是保证了一秒内不同机器不同进程生成objectId不冲突,这后面的三个字节“36236b”是一个自动增加的计数器,用来确保在同一秒内产生的objectId也不会发现冲突,允许256的3次方等于16777216条记录的唯一性。

总的来看,objectId的前4个字节时间戳,记录了文档创建的时间;接下来3个字节代表了所在主机的唯一标识符,确定了不同主机间产生不同的objectId;后2个字节的进程id,决定了在同一台机器下,不同mongodb进程产生不同的objectId;最后通过3个字节的自增计数器,确保同一秒内产生objectId的唯一性。ObjectId的这个主键生成策略,很好地解决了在分布式环境下高并发情况主键唯一性问题,值得学习借鉴。

MongoDB整理笔记のjava MongoDB分页优化的更多相关文章

  1. MongoDB整理笔记の走进MongoDB世界

    本人学习mongodb时间不长,但是鉴于工作的需要以及未来发展的趋势,本人想更深层的认识mongodb底层的原理以及更灵活的应用mongodb,边学边工作实践.  mongodb属于nosql中算是最 ...

  2. MongoDB整理笔记のSharding分片

    这是一种将海量的数据水平扩展的数据库集群系统,数据分表存储在sharding 的各个节点上,使用者通过简单的配置就可以很方便地构建一个分布式MongoDB 集群.MongoDB 的数据分块称为 chu ...

  3. MongoDB整理笔记のReplica Sets

    MongoDB支持在多个机器中通过异步复制达到故障转移和实现冗余.多机器中同一时刻只有一台机器是用于写操作,正因为如此,MongoDB提供了数据一致性的保障.而担当primary角色的机器,可以把读的 ...

  4. MongoDB学习笔记(一) MongoDB介绍及安装(摘)

    MongoDB是一个高性能,开源,无模式的文档型数据库,是当前NoSql数据库中比较热门的一种.它在许多场景下可用于替代传统的关系型数据库或键/值存储方式.Mongo使用C++开发.Mongo的官方网 ...

  5. Mongodb学习笔记一(Mongodb环境配置)

    Mongodb学习 说明: MongoDB由databases组成,database由collections组成,collection由documents组成,document由fileds组成.Mo ...

  6. Mongodb学习笔记二(Mongodb基本命令)

    第二章 基本命令 一.Mongodb命令 说明:Mongodb命令是区分大小写的,使用的命名规则是驼峰命名法. 对于database和collection无需主动创建,在插入数据时,如果databas ...

  7. 【MongoDB数据库】Java MongoDB CRUD Example

    上一页告诉我们MongoDB 命令入门初探,本篇blog将基于上一篇blog所建立的数据库和表完毕一个简单的Java MongoDB CRUD Example.利用Java连接MongoDB数据库,并 ...

  8. MongoDb 学习笔记(一) --- MongoDb 数据库介绍、安装、使用

    1.数据库和文件的主要区别 . 数据库有数据库表.行和列的概念,让我们存储操作数据更方便 . 数据库提供了非常方便的接口,可以让 nodejs.php java .net 很方便的实现增加修改删除功能 ...

  9. MongoDB学习笔记一(MongoDB介绍 + 基本指令 + 查询语句)

    什么是MongoDB MongoDB 是由C++语言编写的,是一个基于分布式文件存储的开源数据库系统. 在高负载的情况下,添加更多的节点,可以保证服务器性能. MongoDB 旨在为WEB应用提供可扩 ...

随机推荐

  1. ubuntu下安装和配置最新版JDK8傻瓜教程

    ubuntu下安装和配置最新版JDK8傻瓜教程 听语音 | 浏览:18940 | 更新:2014-07-14 22:13 | 标签:ubuntu 1 2 3 4 5 6 分步阅读 ubuntu系统通常 ...

  2. AO Identify地图交互

    转自supernever文章 Identify 1.框选要素高亮显示 private void axMapControl1_OnMouseDown(object sender, ESRI.ArcGIS ...

  3. Python基础学习九 数据库备份

    class BakDb(object): def __init__(self,ip,username,passwd,port=3306,path=r'C:\Users\BJQT\Desktop\dat ...

  4. 留用 未验证 js适配根字体大小

    方法一:<script>                (function (doc, win) {                var docEl = doc.documentElem ...

  5. redis centos7

    官网下载tar包 make 修改conf 修改 启动脚本 utils/redis_init_script 开放端口6379

  6. java 项目的jar 反编译后 压缩回去 命令

    jar -cvfm0 123.jar    META-INF\MANIFEST.MF(配置文件第一个位置) BOOT-INF org

  7. BarcodeLib -- 一个精简而不失优雅的条形码生成库

    BarcodeLib -- 一个精简而不失优雅的条形码生成库 引言 在百度进行“C# 条形码”等类似关键字搜索的时候,基本上是使用 ZXing 类库进行条形码的生成.今天我所介绍的是另一款类库 Bar ...

  8. Zookeeper的基本概念和重要特性

    目录 1. 什么是Zookeeper 2. Zookeeper集群角色 3. Zookeeper的数据模型 3.1 Znode的类型 3.2 Znode的结构 4. Zookeeper的事件监听机制 ...

  9. 【POJ2151】Check the difficulty of problems

    题意 某场比赛有M道问题,T支队伍,和数字N给出每支队伍解决每道问题的概率. 问这场比赛满足下面两个条件的概率 1.每支队伍至少做出一道题 2.冠军队至少做出N道题. 分析 条件2是不是可以转化为 至 ...

  10. MAXOS 进程管理

    ps -ef|grep +程序名 注意进程名区分大小写 linux上进程有5种状态:1. 运行(正在运行或在运行队列中等待)2. 中断(休眠中, 受阻, 在等待某个条件的形成或接受到信号)3. 不可中 ...