MongoDB三分钟插入100万数据
文章目录
- 三、小试牛刀露锋芒
- 一、黑云压城城欲摧
- 二、山重水复疑无路
- 四、回首向来萧瑟处,归去,也无风雨也无晴
- 五、按下葫芦浮起瓢
- 六、不计较一城一池之得失
三、小试牛刀露锋芒
现网GhsHis表有几百万数据,但是测试环境只有几万数据,想要模拟现网数据量进行测试。
叮嘱测试用js脚本往数据库插入,结果她还是调了接口进行插入。虽然测试环境MongoDB部署的还是分片集群,但是,还是把测试环境搞挂了。
关键时刻,还得开发上场。
有了测试同事的教训,为了研发环境的安全,我用js脚本先插入了一万条数据小试牛刀。结果呢,执行得很慢。考虑到还有主从复制,从
库查询压力也很大。
肯定不能一条一条插。那就批量插入。刚好MongoDB有支持批量插入的命令insertMany,于是试了一下,果真批量插入,速度不同凡
响,快的不是一星半点。
理论上可行,但实践起来还有很多细节要考虑。
要插入一百万数据,肯定不能一次性插入,我们一次插入一万,分一百次插入。写两层for循环轻松搞定。
插入的数据不能一模一样,比如创建时间和进入历史表的时间不能都一样,所以需要动态设置。
尝试插入了二十条数据,虽然动态设置了时间,但是发现最终所有数据都跟最后一条数据时间一毛一样。
我一个写Java的,为啥要让我写Js脚本?我感觉你这是在为难我胖虎。
钱难挣,屎难吃,工资也不是那么好拿的。于是,我又开始了面向百度编程。
很明显是由于对Js语法不了解导致的。百度了两行代码,试了一下可以。
var b = {};
Object.assign(b, a);
b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);
之前写的是
var b;
b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);
然后就是往数组里面插入数据,使用push方法即可。
统计一下执行脚本的耗时,java的sout使用的是加号拼接,Js使用的是逗号拼接。
var time = new Date().getTime();
print("执行耗时:",new Date().getTime()-time);
让时间具有随机性,调用Js数学类库函数。
NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);
最终脚本
var a = {"customerId": "123456789","username": "ghs@qq.com","pkgId": "66666666","state": "USE_END","price": 1.0,"createDate": NumberLong(1666341382443),"orgId": "963852741","deptId": "147258369","remark": "666","inHisTime": NumberLong(1666346588556)
};var time = new Date().getTime();
for (j = 1; j <= 100; j++) {var arr = [];for (i = 1; i <= 10000; i++) {var b = {};Object.assign(b, a);if (i % 3 == 0) {b.state = "USE_END";} else if (i % 3 == 1) {b.state = "EXPIRE";} else {b.state = "TRANSFER";}b.createDate = NumberLong(new Date().getTime() - Math.round(Math.random() * 10000) + 10000);b.inHisTime = NumberLong(b.createDate + Math.round(Math.random() * 10000) + 100);arr.push(b);}db.GhsHis.insertMany(arr);
}
print("执行耗时:",new Date().getTime()-time);
今天是程序员节,祝大家节日快乐!!!
大功告成???
不,这才是万里长征第一步。革命尚未成功,同志仍需努力!!!
数据是构造好了,有了跟现网差不多的数据量级,但是现网问题的复现、测试还没开始呢!!!
一、黑云压城城欲摧
故事背景:现网导出接口导出Excel数据出现了Id重复,几乎是必现。
测试环境不能复现,距离升级只有三天时间了。时间紧,任务重。
找业务人员要了当时导出的那份Excel,将Id列复制到D:\delete1.txt文件,准备用java代码分析一下。
代码思路,使用高速缓冲字符流一次读取一行,将读取到的Id放入List集合,然后遍历List集合,使用Set集合去重,拿到重复的Id以及下标。
public class IOReader {public static void main(String[] args) throws IOException {BufferedReader bis = null;List<String> list = new ArrayList<>();try {bis = new BufferedReader(new InputStreamReader(new FileInputStream("D:\\delete1.txt")));String str = null;while ((str = bis.readLine()) != null){list.add(str);}} catch (FileNotFoundException e) {e.printStackTrace();} finally {bis.close();}System.out.println(list.size());Set<String> set = new HashSet<>();for(int i=0;i<list.size();i++){if(set.contains(list.get(i))){System.out.println("重复数据:"+list.get(i)+",下标"+i);}else{set.add(list.get(i));}}}
}
通过代码分析发现,确实存在重复Id。因为Id是唯一的,出现重复有悖常理。因为有现表和历史表,表结构大差不差。导出又可以同时导出历史表和现表。查看代码发现,在将现表数据移入历史表的时候,先插入的历史表,然后删除的现表,这样在极端情况下,是可能出现导出重复Id的。
事情会这么简单吗?很明显不会。
二、山重水复疑无路
随着代码的继续深入,我发现对于导出所有的情况,代码已经做了去重处理,并无明显Bug。
带着内心的疑问,我又找到了业务人员。仔细询问了他操作的细节。
操作流水号?操作Id?很明显,业务人员并不关注这些,只是丢给了我一个用户名。
我进入用户表一查,好家伙,一个用户名对应十几个用户。
我又问了他操作时所属的部门,这才将将初步定位了嫌疑人。
案发时间在昨天,还好时间不久,一切证据还未被抹除。
现网Kabana日志只展示七天,超过七天的就只能去磁盘看。需要申请一堆的权限,耗时又费力,大家一般都不愿意申请。
将时间定格在昨天,将嫌疑人锁死在刚才找到的Id。果真寻到了案发现场。
本着不放过一切蛛丝马迹的原则,我仔仔细细地查看了案发时的证据,但并未发现什么特别的有价值的证据。
导出Excel的过程是这样的,前台请求中台,中台请求后台。每次最多导出1000条,中台分次请求后台。
我看了一下每次导出的时间间隔竟然相差14秒,确实有点大。这算是案发现场唯一的收获了。
是中台设置的请求时间还是后台接口竟然如此慢?
我去测试环境试了一下,很快。那就是后台接口慢喽。
案件一下陷入了僵局,扑朔迷离的案情属实让人焦头乱额。
大脑飞速地转动着,思索着还有哪些未考虑到的场景。
我又去看了一下导出Excel,这算是直接证据了,要好好分析一下。
凭借着精湛的业务能力和三年多的工作经验,灵光一现,我机智地发现了出现重复的数据位于每一页开头的位置。
一个Idea浮现在了我的脑海。
我激动地翻找代码,想要佐证自己的想法。果真如我所想,初步定位到了问题所在。
知错,改错,验错。第一步总算是完成了。
改错也很简单,几秒钟搞定。知错几小时,改错几秒钟。
接力棒交接到了测试同事的手中,我总算可以松一口气了。
四、回首向来萧瑟处,归去,也无风雨也无晴
测试按我所说,未能成功复现问题。球呀,又到了我的手里。
作为全场最靓的仔,这点事肯定难不倒英明神武的我。
现网每一次操作时间间隔有14秒,有充足的时间来进行我们想要的操作。测试环境只有三秒,时间不等人,拼的是手速。
交代一下我定位出来的问题:我怀疑是排序字段选择不当造成导出Id重复。
啥?排序字段还能引起导出Id重复?我只是工作了三年的实习生,你别蒙我!
别急,听我细细道来。因为导出是既可以选择现表,也可以选择历史表,它们的排序规则都是一样的:创建时间逆序。问题就出在这里,现表使用创建时间没有问题,但是历史表就有问题了,创建时间早的不一定进入历史表的时间就早。
举个例子,导出的第二页,第1001条到第2000条数据。在正导出第二页的时候,有一个创建时间恰好位于第1001条到第2000条之间的数据被插入,那么根据创建时间逆序排序,原来第2000条数据就会被排到第2001条数据的位置,表现出来就是第2001条数据Id重复。
找出数据量在10000条以上的部门,然后选一个一万条左右的部门导出。
db.GhsHis.aggregate([{$group:{_id:"$deptId",total:{$sum:1}}},{$match:{total:{$gt:10000}}}],{allowDiskUse: true})
管道有100M内存限制。设置allowDiskUse:true,允许使用磁盘存储数据。
由于测试环境数据量用户量不足,一瞬间没有那么多数据失效然后进入历史表。如此苛刻的复现条件只能是手动来提供。
三秒的操作时间,理论上是来得及操作的。但是,可能不具有普适性。测试又复现失败了。
不知道测试同事心中此时作何感想。(叼毛,按你说的方法复现不了问题?)
为了维护我在同事心中的靓仔形象。啪,很快呀,我又写了一个脚本。
var a = {"customerId": "123456789","username": "ghs@qq.com","pkgId": "66666666","state": "USE_END","price": 1.0,"createDate": NumberLong(1666341382443),"orgId": "963852741","deptId": "147258369","remark": "666","inHisTime": NumberLong(1666346588556)
};
function sleep(number){var now = new Date();var exitTime = now.getTime() + number;while (true) {now = new Date();if(now.getTime() > exitTime){return;}}
}
var timeArr=[1641864542173,1641864263779,1637305936014,1637293032528,1636098374840,1624608384592,1621040814675,1617868030123,1617866906466];
timeArr.forEach(function(t){var b = {};Object.assign(b, a);b.createDate = NumberLong(t);sleep(200);db.GhsHis.insert(b);
});
开发呀,你不讲武德,你跟我说用手操作,你竟然偷偷写脚本。
还不是为了操作的普适性,为了问题在测试环境必现,你以为我想写脚本呀?
说起来云淡风轻,实际上斩棘披荆。
老规矩:先解释一下代码思路。找出了九个时间点,这九个时间点都是位于第二页的。然后遍历,每隔200毫秒,以该时间点为创建时间,插入一条数据,代表的是此时有一条创建时间位于第二页的数据被插入历史表,以此来模拟现网操作。
Java线程睡眠一行代码就够了,Js竟然还得自己写,当然都是百度的了。
那这九个时间点是怎么找出来的?
db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}}).sort({"createDate":-1}).skip(1100)
skip那里改成1200,一直到1900,找出9个创建时间。
我将脚本给了测试,让他在第一页导出之后,第二页正在导出的时候,立刻执行该脚本。
结果呢,翻车了,还是没有复现。
大意呀,我没有改进入历史表的时间。因为我改了排序规则,新规则使用进入历史表的时间和创建时间两个字段来逆向排序。
b.inHisTime= NumberLong(new Date().getTime());
又把脚本给了测试,问题成功复现!靓仔的形象得到了强有力的维护!自己的形象要靠自己来维护。
五、按下葫芦浮起瓢
然后,测试升级版本,开始验错,到了校验我改错的时候了。
结果又翻车了!!!每次都能复现问题,改了跟没改一样,我怕不是改了个毛线?
这下好了,靓仔彻底变叼毛了。
细细端详代码,思忖着究竟是哪里出了问题?原来是排序字段的排序方式有问题。
之前是按照创建时间逆向排序,我想都没想,就沿用了之前的方式,根据进入历史表的时间和创建时间两个字段来逆向排序。(当进入历史表时间相同,按照创建时间来逆向排序)
我怎能重蹈前任的覆辙呢?我跟他们又有什么区别?我改错又有什么意义呢?只是从一个坑爬到了另一个坑。
大意,还是大意呀。不仅丢了燕云十八州,连荆襄九郡都丢了。
痛定思痛,痛改前非。
还是时间太紧急了,搞得我很急躁,都不能冷静思考了,犯了如此低级的错误。总得给自己找个借口安慰一下英明神武明察秋毫的自己。
改成正向排序以后,问题果然解决了。问题不会复现了。Nice !
现网问题到这里已经解决了,不会再导出重复的Id了。
六、不计较一城一池之得失
bug已经解决,但是优化永无止境。查询导出1000条耗费14秒,太慢了。
查询慢,第一反应肯定是没加索引。查看现网GhsHis表的索引。
db.GhsHis.getIndexes()
发现表里索引挺多的,但是,排序字段createDate竟然没加索引!
分析一下查询导出语句的执行计划,“stage” : “SORT”,证明排序没有使用索引,在内存中做了排序,且做了全表扫描。
db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}})
.sort({"createDate":-1}).skip(1000).limit(1000).explain("executionStats")
鉴于已经修改了排序规则,所以我给进入历史表时间和创建时间建了联合索引。{background:true}这句一定要加,否则会锁表。
db.GhsHis.ensureIndex({inHisTime:1,createDate:1},{background:true})
state字段与deptId字段数据区分度不高,暂时不加索引。
db.GhsHis.find({"deptId" : "147258369","state":{$in:["EXPIRE","USE_END","TRANSFER"]}}).sort({"inHisTime":1,"createDate":1}).skip(1000).limit(1000).explain("executionStats")
再次查看执行计划,“stage” : “IXSCAN”,说明使用了索引,只扫描了几千条数据,查询时间也只有几十毫秒了。
MongoDB的执行计划比起Mysql而言更加复杂难懂,后续有时间再做深入研究学习,今日尚且浅尝辄止!
MongoDB三分钟插入100万数据相关推荐
- 一分钟内向数据库中批量插入100万数据
罗列一下三种插入方式: 1.动态SQL拼接 批量插入一万条数据:4.7s @Testpublic void dynamicSql() {List<NotifyRecordEntity> n ...
- 4分钟插入1000万条数据到mysql数据库表
准备工作 我用到的数据库为,mysql数据库8.0版本的,使用的InnoDB存储引 创建测试表 CREATE TABLE `product` (`id` int NOT NULL AUTO_INCRE ...
- 插入100万条有随机姓名,随机电话等字段的数据最快需要几秒?
问题描述:往数据库的teachers表里面随机插入100万条带随机姓名,随机电话等字段的数据,需要多少秒? 接下来,我将从1.需要多少秒?2.为什么会这么快(包括PreparedStatement与S ...
- 绝对干货,教你4分钟插入1000万条数据到mysql数据库表,快快进来
我用到的数据库为,mysql数据库5.7版本的 1.首先自己准备好数据库表 其实我在插入1000万条数据的时候遇到了一些问题,现在先来解决他们,一开始我插入100万条数据时候报错,控制台的信息如下: ...
- 力控数据写入mysQL_绝对干货,教你4分钟插入1000万条数据到mysql数据库表,快快进来...
我用到的数据库为,mysql数据库5.7版本的 1.首先自己准备好数据库表 其实我在插入1000万条数据的时候遇到了一些问题,现在先来解决他们,一开始我插入100万条数据时候报错,控制台的信息如下: ...
- 批量往mysql里面插入1000万条数据_绝对干货,教你4分钟插入1000万条数据到mysql数据库表,快快进来...
我用到的数据库为,mysql数据库5.7版本的 1.首先自己准备好数据库表 其实我在插入1000万条数据的时候遇到了一些问题,现在先来解决他们,一开始我插入100万条数据时候报错,控制台的信息如下: ...
- MySQL索引学习(100万数据做对比)
创建表 create table account (id int auto_incrementprimary key,name varchar(11) not null,money decimal(9 ...
- mysql命令行批量添加数据_mysql命令行批量插入100条数据命令
先介绍一个关键字的使用: delimiter 定好结束符为"$$",(定义的时候需要加上一个空格) 然后最后又定义为";", MYSQL的默认结束符为" ...
- 为何插入10万数据只需2秒
文章目录 一.前言 二.问题 1.url后面useServerPrepStmts是什么? 2.url后面rewriteBatchedStatements是什么? 3.这两个参数对语句执行有什么影响? ...
- mysql db 100万行 大小_插入100万行数据
#创建库和表 CREATE DATABASE db1; USE db1; CREATE TABLE t1(id INT,nam VARCHAR(10),age int); #定义一个存储过程,插入数据 ...
最新文章
- 解决linux 系统中Mysql 进程占用CPU 300%故障
- redis 未授权访问利用 两种方式
- [每天进步一点 -- 流水账]第3周
- UVA10870递推关系(矩阵乘法)
- 反素数(luogu 1463)
- 微信重大更新!这特么是为上班摸鱼开发的吧.....(附内测地址)
- 一张图带你了解 Insider Dev Tour 2019中国技术大会
- Mac下关于-您不能拷贝项目“”,因为它的名称太长或包括的字符在目的宗卷上无效。-的删除...
- bzoj3203: [Sdoi2013]保护出题人
- vs2010中moc文件生成方法
- Frontpage网页制作软件,你们还记得吗?
- php post undefined index,PHP 中提示undefined index如何解决(多种方法)
- linux 标准函数注释,Linux 驱动程序中相关函数注释汇总(跟新中)
- 那桃林深处最美的风景
- 前端单页路由《stateman》源码解析
- 利用HTML+JS+CSS实现简单的网页计算器,附html所有源代码,可直接黏贴运行
- 使用 matlab 数字图像处理(七)—— 频率域处理
- 如何通过http协议知道是从那个站点过来的请求_聊聊HTTP的那些事,以及在它背后的“勾当”...
- 关于没有找到MFC80UD.DLL,因此这个程序未能启动.重新安装应用程序可能会修复此问题的解决方案
- Ubuntu串口驱动安装及串口权限设置
热门文章
- 百度2017春招笔试真题编程题集合 [编程题]买帽子
- 高德地图E/libEGL: call to OpenGL ES API with no current context (logged once per thread)
- Note: further occurrences of HTTP request parsing errors will be logged at DEBUG level.错误解决
- php like 中文,punycode和中文转换 phpThe Twitter-clone/twitter-like sites collection
- Unity3D的3D音效的实现
- 浙江大学黄杨思博计算机学院,竺可桢学院2010-2011学年荣誉称号发文名单
- PHP - 收藏集 - 掘金
- IE提示当前安全设置不允许下载该文件怎么办?
- 如何设置无需fn直接按F1~F10(HP Pavilion Notebook )
- linux 下安装redis并设置开机自启动