您现在的位置是:群英 > 开发技术 > 编程语言
为什么mysql插入数据会变慢,原因是什么
Admin发表于 2022-05-27 18:02:253381 次浏览
这篇文章主要给大家介绍“为什么mysql插入数据会变慢,原因是什么”的相关知识,下文通过实际案例向大家展示操作过程,内容简单清晰,易于学习,有这方面学习需要的朋友可以参考,希望这篇“为什么mysql插入数据会变慢,原因是什么”文章能对大家有所帮助。

mysql插入数据变慢的原因:1、由主码、外码、索引造成的插入效率降低;2、由于使用for循环不停执行这个方法来插入;3、未能及时释放查询结果。

推荐:《mysql视频教程》《java教程》

最近的项目需要导入大量的数据,插入的过程中还需要边查询边插入。插入的数据量在100w左右。一开始觉得100w的数据量不大,于是就插啊插,吃了个饭,回来一看,在插入了50多w条数据后,每秒就只能插10条了。。觉得很奇怪,为啥越插越慢呢? 于是就开始分析插入的时间损耗,想到了如下的解决方案:(mysql使用的INNODB引擎

1.分析是否是由主码,外码,索引造成的插入效率降低

主码:由于主码是每张表必须有的,不能删除。而mysql会对主码自动建立一个索引,这个索引默认是Btree索引,因此每次插入数据要额外的对Btree进行一次插入。这个额外的插入时间复杂度约为log(n)。这个索引无法删除,因此无法优化。但是每次插入的时候,由于主码约束需要检查主码是否出现,这又需要log(n),能否减少这个开销呢?答案是肯定的。我们可以设置主码为自增id AUTO_INCREMENT ,这样数据库里会自动记录当前的自增值,保证不会插入重复的主码,也就避免了主码的重复性检查。

外码:由于我的项目的插入表中存在外码,因此每次插入时需要在另一张表检测外码存在性。这个约束是与业务逻辑相关的,不能随便删除。并且这个时间开销应当是与另一张表大小成正比的常数,不应当越插入越慢才对。所以排除。

索引:为了减少Btree插入的时间损耗,我们可以在建表时先不建索引,先将所有的数据插入。之后我们再向表里添加索引。该方法确实也降低了时间的开销。

经过以上的折腾,再进行测试,发现速度快了一点,但是到了50w条后又开始慢了。看来问题的关键不在这里。于是继续查资料,又发现了个关键问题:

2.将单条插入改为批量插入(参考:点击打开链接)

由于java中的executeUpdate(sql)方法只是执行一条sql操作,就需要调用sql里的各种资源,如果使用for循环不停的执行这个方法来插入,无疑是开销很大的。因此,在mysql提供了一种解决方案:批量插入。 也就是每次的一条sql不直接提交,而是先存在批任务集中,当任务集的大小到了指定阈值后,再将这些sql一起发送至mysql端。在100w的数据规模中,我将阈值设置为10000,即一次提交10000条sql。最后的结果挺好,插入的速度比之前快了20倍左右。批量插入代码如下:

public static void insertRelease() {  
        Long begin = new Date().getTime();  
        String sql = "INSERT INTO tb_big_data (count, create_time, random) VALUES (?, SYSDATE(), ?)";  
        try {  
            conn.setAutoCommit(false);  
            PreparedStatement pst = conn.prepareStatement(sql);  
            for (int i = 1; i <= 100; i++) {  
                for (int k = 1; k <= 10000; k++) {  
                    pst.setLong(1, k * i);  
                    pst.setLong(2, k * i);  
                    pst.addBatch();  
                }  
                pst.executeBatch();  
                conn.commit();  
            }  
            pst.close();  
            conn.close();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
        Long end = new Date().getTime();  
        System.out.println("cast : " + (end - begin) / 1000 + " ms");  
    }

3.一条UPDATE语句的VALUES后面跟上多条的(?,?,?,?)

这个方法一开始我觉得和上面的差不多,但是在看了别人做的实验后,发现利用这个方法改进上面的批量插入,速度能快5倍。后来发现,mysql的导出sql文件中,那些插入语句也是这样写的。。即UPDATE table_name (a1,a2) VALUES (xx,xx),(xx,xx),(xx,xx)... 。也就是我们需要在后台自己进行一个字符串的拼接,注意由于字符串只是不停的往末尾插入,用StringBuffer能够更快的插入。下面是代码:

public static void insert() {  
        // 开时时间  
        Long begin = new Date().getTime();  
        // sql前缀  
        String prefix = "INSERT INTO tb_big_data (count, create_time, random) VALUES ";  
        try {  
            // 保存sql后缀  
            StringBuffer suffix = new StringBuffer();  
            // 设置事务为非自动提交  
            conn.setAutoCommit(false);  
            // Statement st = conn.createStatement();  
            // 比起st,pst会更好些  
            PreparedStatement pst = conn.prepareStatement("");  
            // 外层循环,总提交事务次数  
            for (int i = 1; i <= 100; i++) {  
                // 第次提交步长  
                for (int j = 1; j <= 10000; j++) {  
                    // 构建sql后缀  
                    suffix.append("(" + j * i + ", SYSDATE(), " + i * j  
                            * Math.random() + "),");  
                }  
                // 构建完整sql  
                String sql = prefix + suffix.substring(0, suffix.length() - 1);  
                // 添加执行sql  
                pst.addBatch(sql);  
                // 执行操作  
                pst.executeBatch();  
                // 提交事务  
                conn.commit();  
                // 清空上一次添加的数据  
                suffix = new StringBuffer();  
            }  
            // 头等连接  
            pst.close();  
            conn.close();  
        } catch (SQLException e) {  
            e.printStackTrace();  
        }  
        // 结束时间  
        Long end = new Date().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插入数据会变慢,原因是什么的介绍,本文内容仅供参考,有需要的朋友可以借鉴了解看看,希望对大家学习或工作,想要了解更多欢迎关注群英网络,小编每天都会为大家更新不同的知识。

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。

标签: mysql插入数据
相关信息推荐
2022-08-25 17:46:14 
摘要:这篇文章主要介绍了PHP利用curl发送HTTP请求的实例,文中示例代码非常详细,帮助大家更好的理解和学习,感兴趣的朋友可以了解下
2022-07-11 17:49:22 
摘要:实现步骤:1、利用array_filter()函数过滤数组,将数组中的空元素删除,语法“array_filter(原数组)”,会返回一个过滤后的数组;2、利用count()函数获取原数组的长度和过滤后数组的长度;3、检查获取的两个数组长度是否相等,语法“原数组长度===过滤数组长度”,如果相等则数组元素均不为空,反之则有数组元素为空。
2022-05-10 16:44:01 
摘要:给大家带来一篇关于PHP匿名函数与注意事项实例分析的相关教程文章,内容涉及到PHP、匿名函数、详解PHP匿名函数与注意事项等相关内容,更多关于详解PHP匿名函数与注意事项的内容希望能够帮助到大家。
云活动
推荐内容
热门关键词
热门信息
群英网络助力开启安全的云计算之旅
立即注册,领取新人大礼包
  • 联系我们
  • 24小时售后:4006784567
  • 24小时TEL :0668-2555666
  • 售前咨询TEL:400-678-4567

  • 官方微信

    官方微信
Copyright  ©  QY  Network  Company  Ltd. All  Rights  Reserved. 2003-2019  群英网络  版权所有   茂名市群英网络有限公司
增值电信经营许可证 : B1.B2-20140078   粤ICP备09006778号
免费拨打  400-678-4567
免费拨打  400-678-4567 免费拨打 400-678-4567 或 0668-2555555
微信公众号
返回顶部
返回顶部 返回顶部