前言:最近在项目中需要从一个接口获取大量的数据,然后插入到自己的数据库去。项目使用的是SSM框架结构,数据库是Oracle,最开始
是在java里面for循环一条一条插进去的写法,后面发现这样太耗时,就试了一下批量插入的SQL语句,果然速度提升了非常多,如果再使用
多线程的话,速度还会提升一些。
那这样就使我好奇起来,那就是Oracle一次性最多支持多少条的批量插入呢?因为不搞清楚这个,有可能到后面的时候返回的数据量
超过Oracle能够支持的批量插入数据量,就会报错。所以还是应该搞清楚。同时也测试一下MySQL和Oracle在方面的对比
下面是测试数据库的代码
Spring 配置文件
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.2.xsd"> <context:component-scan base-package="com.test"></context:component-scan> <!-- 1. 数据源 : DriverManagerDataSource 链接Oracle 的数据库 --> <bean id="oracleDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="oracle.jdbc.driver.OracleDriver" /> <property name="url" value="jdbc:oracle:thin:@localhost:1521:orcl" /> <property name="username" value="scott" /> <property name="password" value="orcl"/> </bean> <!-- 1. 数据源 : DriverManagerDataSource MySQL 的数据库 --> <bean id="mysqlDataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test?&useSSL=false&allowMultiQueries=true" /> <property name="username" value="root" /> <property name="password" value="root"/> </bean> <!--使用动态数据源,因为这里面会同时使用到两个数据库--> <bean id="dynamicDataSource" class="com.test.dynamic.datasource.DynamicDataSource"> <property name="targetDataSources"> <map key-type="java.lang.String"> <!-- 指定lookupKey和与之对应的数据源 --> <entry key="dataSource1" value-ref="oracleDataSource"></entry> <entry key="dataSource2" value-ref="mysqlDataSource"></entry> </map> </property> <!-- 这里可以指定默认的数据源 --> <property name="defaultTargetDataSource" ref="oracleDataSource" /> </bean> <!-- 2. mybatis的SqlSession的工厂: SqlSessionFactoryBean dataSource / typeAliasesPackage --> <!--配置SessionFactory--> <bean id="sessionFactory" class=" org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dynamicDataSource"/> <property name="typeAliasesPackage" value="com.test.dto"/> </bean> <!-- 3. mybatis自动扫描加载Sql映射文件 : MapperScannerConfigurer sqlSessionFactory / basePackage --> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="com.test.mapper"/> <property name="sqlSessionFactoryBeanName" value="sessionFactory"/> </bean> <!-- 4. 事务管理 : DataSourceTransactionManager --> <!--<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="dataSource"/> </bean> <!– 5. 使用声明式事务 –> <tx:annotation-driven transaction-manager="txManager"/> --> </beans>以上的配置文件学习过SSM的人,应该不陌生。需要提到的一点是项目因为要使用到Oracle和MySQL两个数据库进行测试,所以这里使用了动态数据源,注意在配置SessionFactory的dataSource时,就不在引入某一具体的dataSource了,而是引用动态数据源dynamicDataSource 这个bean,这样需要切换数据源的时候,只需要在java代码里面调用下面的代码并且传入你想要调用的数据源
DynamicDataSourceHolder.setDataSource("dataSource2");这样就可以在代码里面动态的切换数据库了。测试
package com.test; import com.test.dto.TestPressure; import com.test.dynamic.datasource.DynamicDataSourceHolder; import com.test.mapper.TestPressureMapper; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; import java.math.BigDecimal; import java.util.ArrayList; import java.util.List; /** * Created by xx on 2017-08-03. */ @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = {"classpath:beans.xml"}) public class TestDataBasePressure { @Autowired private TestPressureMapper pressureMapper; @Autowired private SqlSessionFactory sessionFactory; @Test public void testStart() throws Exception{ List<TestPressure> pressureList = new ArrayList<TestPressure>(); TestPressure p1; for (int i=1;i<=1000;i++){ p1 = new TestPressure(); p1.setPhone("1507486"+i); p1.setAddr("上海市浦东新区XX路XX号:"+i); p1.setAge(new BigDecimal(i)); p1.setCountry("中国"+i); p1.setId(new BigDecimal(1)); p1.setDepartment("oracle:"+i); p1.setName("张三:"+i); p1.setSex("男:"+i); pressureList.add(p1); } System.out.println("---------测试开始----------"); //testForInsertForeach(pressureList,"Oracle11g"); // testBatch(pressureList,"Oracle11g"); multipleThreadBatchInsert(pressureList,"Oracle11g"); System.out.println(); /** * 切换数据源 为mysql */ DynamicDataSourceHolder.setDataSource("dataSource2"); //testForInsertForeach(pressureList,"MySQL"); //testBatch(pressureList,"MySQL"); multipleThreadBatchInsert(pressureList,"MySQL"); //System.out.println("\n\n"); //testBatch(pressureList); //System.out.println("\n\n"); // multipleThreadBatchInsert(pressureList); System.out.println("---------测试结束----------"); } /** * 测试for循环一条一条插入 * @param pressures * @param database */ public void testForInsertForeach(List<TestPressure> pressures,String database){ System.out.println("------------------for循环插入开始执行-------------"); System.out.println("Size:"+pressures.size()); System.out.println("database:"+database); long start = System.currentTimeMillis(); System.out.println("开始时间:"+start); pressures.forEach(e->{ pressureMapper.insert(e); }); long end = System.currentTimeMillis(); System.out.println("结束时间:"+end); System.out.println(database+"执行时间:"+(end-start)); System.out.println("------------------for循环插入结束执行-------------"); } /** *测试批量一次性插入 * */ public void testBatch(List<TestPressure> pressures,String database){ System.out.println("-------------------批量插入开始执行---------------"); System.out.println("Size:"+pressures.size()); System.out.println("database:"+database); long start = System.currentTimeMillis(); System.out.println("开始时间:"+start); pressureMapper.batchInsert(pressures); long end = System.currentTimeMillis(); System.out.println("结束时间:"+end); System.out.println(database+"执行时间:"+(end-start)); System.out.println("-------------------批量插入结束执行---------------"); } /** * 测试多线程分批插入数据 * @param pressures * @param database * @throws Exception */ public void multipleThreadBatchInsert(List<TestPressure> pressures,String database) throws Exception{ System.out.println("-------------------多线程批量插入开始执行---------------"); System.out.println("Size:"+pressures.size()); System.out.println("database:"+database); int threadSize=10; System.out.println("线程数:"+threadSize); long start = System.currentTimeMillis(); System.out.println("开始时间:"+start); int taskSize = pressures.size()/threadSize; for(int i=0;i<threadSize;i++) { List<TestPressure> ps = pressures.subList(i*taskSize,i==threadSize-1?pressures.size():(i+1)*taskSize); Thread t = new Thread(() -> { pressureMapper.batchInsert(ps); }); t.start(); t.join(); } long end = System.currentTimeMillis(); System.out.println("结束时间:"+end); System.out.println(database+"执行时间:"+(end-start)); System.out.println("-------------------多线程批量插入结束执行---------------"); } } 我使用的是JUnit结合Spring的测试,其实直接写Main方法的测试也是可以的。另外上面的代码中,我不止写了批量插入的测试,还写了for循环一条一条插入和多线程批量插入的测试。
结果:
还有其他的测试结果没有上传,有兴趣的可以自行下载代码测试
结论:
Oracle批量和for循环比较:批量完胜!
Oracle批量和多线程批量:多线程快一丢丢
Oracle插入数据上限:一次性插入10000条会报错,5000条不会,具体最大能插入多少条应该在5000到10000条之间,但是没有测试出来,这样当
数据量超过5000条之后可以拆分成每批小于5000的数据量插入,这样就能保证不出现错误。
Oracle VS MySQL
不管是批量插入,还是for循环插入,还是多线程批量插入。MySQL都完胜Oracle,但是我看网上都是说Oracle的效率比MySQL高,
难道是我的数据库设置哪里出问题了么? Oracle 和MySQL测试对比数据量有500,1000。那么至少可以保证1到1000之间,Oracle的
效率是比不上MySQL的。
测试环境:
win10
jdk1.8
MySQL5.7
Oracle 11g
Spring Mybatis版本可以查看Maven配置文件
附:测试项目里的有些代码没有放在页面上(包括:动态数据源代码,Mapper接口,Mapper xml 文件,等等),因为篇幅过长,需要代码的请自行下载。
下载代码:Github