您的位置:首页 > 数据库

记一次Mock100万数据到数据库遇到的问题

2016-07-03 14:20 218 查看

背景

需要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之类,应该也差不多,有空可以写代码试验一下。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  mysql 批量插入