最近项目在做网站用户数据新访客统计,数据存储在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. centos 6.3 kickstart 装机卡在 unsupported hardware detected 页面

    起因 最近厂里一批 dell 新机器 centos 6.3 装机卡在 Unsupported Hardware Detected 页面,要人肉点击 OK 才能继续装机: Unsupported Har ...

  2. [Z]LaTeX入门教程

    LaTeX入门教程 Contents TEX/LATEX是什么? 为什么要用TEX/LATEX? 安装 开始使用 数学符号使用中文文章的各个部分表格 行内公式与行间公式 上标与下标 常见的数学公式 行 ...

  3. 关于启动MongDB的mongod.exe文件闪退的问题

    昨天学mongdb的时候,遇到了mongod.exe闪退的问题,解决办法很简单: 你可以不执行mongod.exe,直接用命令行操作 在你安装mongdb的盘的根目录下创建一个data文件夹,一定要在 ...

  4. cdoj32-树上战争(Battle on the tree) 【记忆化搜索】

    http://acm.uestc.edu.cn/#/problem/show/32 树上战争(Battle on the tree) Time Limit: 12000/4000MS (Java/Ot ...

  5. PencilWang博客目录

    在这里有一坨目录,以后自己和别人看随笔都会方便很多 一 .刷题相关 1.BZOJ BZOJ1001(最大流,最短路)(EASY+)   BZOJ1002(数学)(NORMAL+)  BZOJ1003( ...

  6. 小程序动态生成二维码,生成image图片

    前端: <image src="{{img_usrl}}" style="width:100%;height:104px;" bindlongtap=&q ...

  7. @property 修饰符

    原子性--- nonatomic 特质 在默认情况下,由编译器合成的方法会通过锁定机制确保其原子性(atomicity).如果属性具备 nonatomic 特质,则不使用同步锁.请注意,尽管没有名为“ ...

  8. Emgu CV的配置以及在VS 2012中进行图像处理的步骤和实例

    说明: 1.所使用的Emgu CV是目前的最新版本3.1.0,下载链接为:https://sourceforge.net/projects/emgucv/files/emgucv/3.1.0/(我选的 ...

  9. Zookeeper使用--Java API

    1  创建节点 创建节点有异步和同步两种方式.无论是异步或者同步,Zookeeper都不支持递归调用,即无法在父节点不存在的情况下创建一个子节点,如在/zk-ephemeral节点不存在的情况下创建/ ...

  10. Springboot-读取核心配置文件及自定义配置文件

    读取核心配置文件 核心配置文件是指在resources根目录下的application.properties或application.yml配置文件,读取这两个配置文件的方法有两种,都比较简单. 核心 ...