mysql数据导入慢,mysql 突然变慢
如何解决写爬虫IP受阻的问题?立即使用。
推荐:《mysql视频教程》 《java教程》
近期项目需要导入大量数据,查询时需要插入。插入的数据量在100w左右。一开始觉得100w的数据量不大,就插上插下。吃完饭回来,看到插了50多条数据后,每秒只能插10条。很奇怪。为什么越来越慢?于是我开始分析插入时间损失,想出了以下解决方案:(mysql使用的INNODB引擎)
1.分析是否是由主码,外码,索引造成的插入效率降低
主密钥:因为每个表都需要主密钥,所以不能删除它。Mysql会自动建立主代码的索引,默认是Btree索引,所以每次插入数据都要多插入一次Btree。这个额外的插入时间复杂度大约是log(n)。该索引无法删除,因此无法优化。但是我们每次插入的时候都需要检查是否出现了主键,因为主键约束,需要log(n)。我们能减少这种开销吗?答案是肯定的。我们可以将主密钥设置为auto-increment id _ increment,这样当前的自动增量值就会自动记录在数据库中,这样就保证了不会插入重复的主密钥,从而避免了主密钥的重复检查。
外来代码:因为我的项目的插入表中存在外来代码,所以每次插入时都需要检查另一个表中是否存在外来代码。这个约束关系到业务逻辑,不能随便删除。而且这个时间开销应该是一个与另一个表的大小成正比的常数,不应该插入得更慢。所以排除它。
索引:为了减少Btree插入的时间消耗,我们可以在创建表之前插入所有数据,而不用索引。然后我们向表中添加索引。这种方法确实减少了时间开销。
经过上面的折腾,然后测试,发现速度快了一点,但是到了50w就开始变慢了。看来问题的关键不在这里。于是继续查资料,发现了一个关键问题:
2.将单条插入改为批量插入(参考:点击打开链接)
因为java中的executeUpdate(sql)方法只执行一个sql操作,所以需要调用sql中的各种资源。如果使用for循环来执行这个方法进行插入,无疑会耗费很大。因此,mysql提供了一个解决方案:批量插入。也就是说,每个sql不是直接提交的,而是首先存在于批处理任务集中。当任务集的大小达到指定的阈值时,这些sql被一起发送到mysql。在100w的数据规模中,我将阈值设置为10000,即我一次提交10000条sql。最终效果相当不错,插入速度比之前快了20倍左右。批量插入代码如下:
公共静态void insertRelease() {
长开始=新日期()。getTime();
string SQL= INSERT INTO TB _ big _ data(count,create_time,random)值(?SYSDATE(),);
尝试{
conn . set auto commit(false);
prepared statement PST=conn . prepare statement(SQL);
for(int I=1;i=100i ) {
for(int k=1;k=10000k ) {
pst.setLong(1,k * I);
pst.setLong(2,k * I);
PST . add batch();
}
PST . execute batch();
conn . commit();
}
PST . close();
conn . close();
} catch (SQLException e) {
e . printstacktrace();
}
长结束=新日期()。getTime();
system . out . println( cast:(end-begin)/1000 ms );
}3.一条UPDATE语句的VALUES后面跟上多条的(?,?,?,?)
一开始我以为这个方法和上面的差不多,但是看了别人做的实验,发现用这个方法改进上面的批量插入可以快5倍。后来发现mysql导出的sql文件中的那些insert语句也是这样写的。即更新table _ name (a1,a2)值(xx,xx),(xx,xx),(xx,xx).也就是说,我们需要在后台自己拼接一个字符串。请注意,因为字符串只是不断地插入到末尾,所以使用StringBuffer可以更快地插入。代码如下:
公共静态void insert() {
//开放时间
长开始=新日期()。getTime();
//sql前缀
字符串前缀=插入到tb_big_data (count,create_time,random)值中;
尝试{
//保存sql后缀
StringBuffer后缀=new string buffer();
//将事务设置为非自动提交
conn . set auto commit(false);
//语句ST=conn . create Statement();
//pst会比st好。
prepared statement PST=conn . prepare statement(“”);
//外部循环,已提交事务的总数
for(int I=1;i=100i ) {
//第一次提交的步长
for(int j=1;j=10000j ) {
//构建sql后缀
后缀. append(( j * I ,SYSDATE(), i * j
* Math.random()),);
}
//构建完整的sql
string SQL=prefix suffix . substring(0,suffix . length()-1);
//添加执行sql
PST . add batch(SQL);
//执行操作
PST . execute batch();
//提交事务
conn . commit();
//清除最后添加的数据
suffix=new string buffer();
}
//第一类连接
PST . close();
conn . close();
} catch (SQLException e) {
e . printstacktrace();
}
//结束时间
长结束=新日期()。getTime();
//耗时
system . out . println( cast:(end-begin)/1000 ms );
}做了上面的优化,发现了一个很痛苦的问题。虽然最初的插入速度确实快了几倍,但是插入50w条数据后,插入速度总会突然变得很慢。这种慢插就是断崖式的突变,于是我苦苦思索,不小心打开了系统的资源管理器。第一眼就发现java占用的内存在飙升。突然想到:是不是内存溢出了?
4.及时释放查询结果
在我的数据库查询语句中,pres=con.prepareStatement(sql)用于保存一个sql执行状态,resultSet=pres.executeQuery用于保存查询结果集。在搜索和插入的过程中,我的代码一直没有释放查询结果,导致它不断占用内存空间。我的插入在50w左右的时候,内存空间已经满了,所以数据库的插入开始用磁盘代替内存,所以插入速度开始变得很低。所以每次使用pres和resultSet后,我都添加了释放它们空间的语句:resultSet . close();pres . close();再次测试,果然内存没有飙升,插数据到50w后速度也没有下降。原来问题的本质就在这里!以上是mysql插入数据慢的原因的详细内容。更多请关注我们的其他相关文章!
郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。