记一次Mock100万数据到数据库遇到的问题
来源:互联网 发布:php的就业前景好 编辑:程序博客网 时间:2024/04/30 04:00
背景
需要Mock100万数据到数据库。
逐条插入
如果一条一条的插入,模拟1000条,花费了8秒,100万的话,大约花费3个小时,太慢。
批量插入
问题出现
1000条一次进入插入。
consoleLogger.info("This Batch, {}, Time start : {}", batchTotal, s);orderMapper.batchInsert(orderList);consoleLogger.info("This Batch, {}, Time elapsed : {}", batchTotal, System.currentTimeMillis() - s);
按照理论,应该比逐条快很多,但是,结果花费了14秒,非常奇怪,开始查找原因。
求助Google
看到两篇有价值的帖子:
http://www.cnblogs.com/aicro/p/3851434.html
http://blog.jobbole.com/85657/
试着改成100条一个批次,很神奇,1000条数据插入,10个循环,总计60毫秒就完成了。难道用MyBatis操作MySQL数据库的批量操作,对于10条一批和1000条一批,性能差别这么大?
不可理解,本着根本上解决问题的思路,继续追根溯源。
加Log
<logger name="org.mybatis.spring" additivity="true" level="${logger_level}"> <appender-ref ref="serviceAppender" /></logger><logger name="org.apache.mybatis" additivity="true" level="${logger_level}"> <appender-ref ref="serviceAppender" /></logger>
打出一些日志,但是没有参考价值。
Debug
于是Debug,一直跟下去,跟了很久,终于在这里发现问题。
public static String showSql(Configuration configuration, BoundSql boundSql) { String sql = boundSql.getSql().replaceAll("[\\s]+", " "); try{ Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings.size() > 0 && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { sql = sql.replaceFirst("\\?", getParameterValue(parameterObject)); } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); if (metaObject.hasGetter(propertyName)) { Object obj = metaObject.getValue(propertyName); sql = sql.replaceFirst("\\?", getParameterValue(obj)); } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); sql = sql.replaceFirst("\\?", getParameterValue(obj)); } } } } }catch (Exception e){ logger.error("showSql invoke error:{}",e); } return sql;}
原来数据库的批量插入操作,很快就完成返回了,然后,我们自己工程中写的慢查询拦截器,对于超过300毫秒,会有拦截处理,把?替换成参数值,大约到Log里面,这个过程非常慢。
于是临时注释掉这个代码,测试了一下,批量插入非常快了,并且是批量插入的行数越多,越快。
为什么批量快
批量执行效率高的主要原因是合并后日志量(MySQL的binlog和innodb的事务)减少了,降低日志刷盘的数据量和频率,从而提高效率。
通过合并SQL语句,同时也能减少SQL语句解析的次数,减少网络传输的IO。
为什么这个方法慢
String是Immutable,因为对于批量 parameterMappings很多,几万个,于是反复进行String.replaceFirst,每次执行这个操作,都会复制一份新的字符串,于是内存复制了几万个次,于是就很慢了。
解决办法,用Parttern和Match,这种方式,在内存只会产生一个备份,然后就会从头到尾替换下去,经测试,性能果然提高很多,不再是14秒,只是几百毫秒。
public static String showSql(Configuration configuration, BoundSql boundSql) { String sql = boundSql.getSql().replaceAll("[\\s]+", " "); StringBuffer sqlBuilder = new StringBuffer(); try{ Pattern pattern = Pattern.compile("\\?"); Matcher matcher = pattern.matcher(sql); Object parameterObject = boundSql.getParameterObject(); List<ParameterMapping> parameterMappings = boundSql.getParameterMappings(); if (parameterMappings.size() > 0 && parameterObject != null) { TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) { if(matcher.find()) { matcher.appendReplacement(sqlBuilder, getParameterValue(parameterObject)); } } else { MetaObject metaObject = configuration.newMetaObject(parameterObject); for (ParameterMapping parameterMapping : parameterMappings) { String propertyName = parameterMapping.getProperty(); if (metaObject.hasGetter(propertyName)) { Object obj = metaObject.getValue(propertyName); if(matcher.find()) { matcher.appendReplacement(sqlBuilder, getParameterValue(obj)); } } else if (boundSql.hasAdditionalParameter(propertyName)) { Object obj = boundSql.getAdditionalParameter(propertyName); if(matcher.find()) { matcher.appendReplacement(sqlBuilder, getParameterValue(obj)); } } } } } matcher.appendTail(sqlBuilder); }catch (Exception e){ logger.error("showSql invoke error:{}",e); } return sqlBuilder.toString();}
然后,进一步,又通过对于某些不想被拦截的SQL,头部增加/*NoMonitor*/的方式,避免进行字符串处理。
于是问题彻底解决,插入100万数据,10000条一个批次,大约需要6分钟。
如果不是MySQL呢
向所有的持久化存储插入数据,批量都会比逐条快得多。
我过去试过的Solr和Redis,大约是几十倍的差别。
其他如ES、Mongo之类,应该也差不多,有空可以写代码试验一下。
- 记一次Mock100万数据到数据库遇到的问题
- 记一次面试中遇到的问题。
- 一次插入上万条数据遇到的问题
- executemany存万得数据到数据库
- 50万条数据保存到数据库
- 从excel表导入数据到sql server 2005数据库遇到的问题
- 记一次从阿里云的rds恢复备份数据到自建数据库
- 记一次处理 list 的 remove 方法遇到的问题
- 记一次遇到的文件乱码的问题
- 记一次Spring aop的所遇到的问题
- 记一次代码部署时遇到的问题
- 记一次程序更新时遇到的问题
- 记一次在LAMP中遇到的问题
- 记一次Redis设置主从复制时遇到的问题
- 记一次编写python爬虫遇到的问题
- 如何将本地sqlserver的2000万数据迁移到虚拟机中的Oracle数据库里面
- 数据库2000迁移到2005遇到的问题
- sql server 2005数据库迁移到oracle10g遇到的问题
- 进程与线程之间的关系与区别分析
- 剑指offer——斐列那契数列(递归)
- 16.7.3菲波那契数列
- centos 用nat连接的方式,
- bootstrap
- 记一次Mock100万数据到数据库遇到的问题
- 本人在学习笔记
- Git版本控制工具在Android Studio中的使用
- Hadoop之HDFS客户端的权限错误:Permission denied
- 创建一个Android程序
- 教你怎么十秒解决百张图片帧动画oom终极骚问题
- 函数指针和指针函数 区别
- 整合Struts2,Hibernate和Spring的一个简单例子
- JavaScript笔记整理 —— XMLHttpRequest对象