经常有海量插入数据的需求,几十万,几千万甚至更多。常用的框架如mybatis、hibernate都支持批量插入,其基本原理都是重写insert语句(讲多个汇聚成一个)。
从sql语句比较。
非批量插入
INSERT INTO `t` (`name`) VALUES ("name_1");
INSERT INTO `t` (`name`) VALUES ("name_2");
批量插入
INSERT INTO `t` (`name`) VALUES ("name_1"),("name_2");
对海量数据插入,选择批量插入是必要的。
前置条件:
- 数据库mysql
- 驱动jbbc
- 模型带自增长主键
选择自增主键的原因:使用数据库自身的内部轻量级锁机制,更高效;索引优化;使用方便。
JDBC批量插入
mybatis或hibernate的批量插入是以JDBC的批量插入为为为基础的。原生的jdbc批量插入,如上,手动拼接sql,但此种带来的sql注入等问题不可避免,因此,开发中极不推荐此种方法。
对于大批量的数据插入,讲究效率,首推JDBC,并不推荐mybatis或hibernate。
代码执行JDBC批量插入
long start = System.currentTimeMillis();
Class.forName("com.mysql.jdbc.Driver");
Connection connection = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/test?useSSL=true&rewriteBatchedStatements=true", "root", "1234");
connection.setAutoCommit(false);
PreparedStatement cmd = connection.prepareStatement("insert into `t` (`name`) values(?)");
for (int i = 1; i <= 999999; i++) {
cmd.setString(1, "name_" + i);
cmd.addBatch();
// 一次想insert 5000条
if (i % 5000 == 0 && i != 0) {
cmd.executeBatch();
cmd.clearBatch();
}
//每20000条提交一次事物
if (i % 20000 == 0 && i != 0) {
cmd.executeBatch();
cmd.clearBatch();
connection.commit();
}
}
cmd.executeBatch();
cmd.clearBatch();
connection.commit();
cmd.close();
connection.close();
long end = System.currentTimeMillis();
System.out.println(end - start);
只有配置了rewriteBatchedStatements,批量才会生效
Hibernate批量插入
hibernate有个hibernate.jdbc.batch_size属性,配置批量insert的size,但是hibernate基于transactional write-behind的策略,对主键自增(strategy=GenerationType.IDENTITY)策略不予支持批量操作。不使用主键自增。
@Autowired
LocalSessionFactoryBean factory;
@Test
public void contextLoads() throws Exception {
long start = System.currentTimeMillis();
/**hibernate.jdbc.batch_size不会设置很大,在30-50左右,但是大批量的插入,显然此值偏小。
* 大批量的插入也不推荐使用hibernate的批量插入(多余的封装处理环节,建议jdbc)
* */
factory.getHibernateProperties().setProperty("hibernate.jdbc.batch_size", "5000");
factory.afterPropertiesSet();
Session session = factory.getObject().openSession();
Transaction tx = session.beginTransaction();
for ( int i=1; i<=200000; i++ ) {
T t = new T();
t.setId(i);
t.setName("name-"+i);
session.save(t);
//手动控制强刷,一般取值<=hibernate.jdbc.batch_size,大于没意义
if ( i % 5000 == 0 && i != 0) {
session.flush();
session.clear();
}
if ( i % 50000 == 0 && i != 0) {
session.flush();
session.clear();
tx.commit();
tx = session.beginTransaction();
}
}
session.flush();
session.clear();
tx.commit();
session.close();
long end = System.currentTimeMillis();
System.out.println(end - start);
}
为了查看批量插入的数据,可使用小工具
datasource-proxy
Mybatis批量插入
mybatis有两者批量插入
@Autowired
SqlSessionFactory sqlSessionFactory;
@Test
public void contextLoads() {
long begin= System.currentTimeMillis();
SqlSession session =null;
TeeMapper mapper = null;
session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
mapper = session.getMapper(TeeMapper.class);
for (int i = 1; i < 200000; i++) {
mapper.insert(new Tee("name-"+i,i));
if (i%50000==0) {
session.flushStatements();
session.clearCache();
}
}
session.commit();
System.out.println(System.currentTimeMillis()-begin);
}
@Test
public void contextLoads1() {
long begin= System.currentTimeMillis();
SqlSession session =null;
TeMapper mapper = null;
session = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
mapper = session.getMapper(TeMapper.class);
List<Te> list = new ArrayList<>();
for (int i = 1; i < 200000; i++) {
list.add(new Te("name-"+i));
if (i%50000==0) {
mapper.batchinsert(list);
list.clear();
session.clearCache();
}
}
mapper.batchinsert(list);
list.clear();
session.clearCache();
session.commit();
System.out.println(System.currentTimeMillis()-begin);
}
xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="zl.example.mapper.TeMapper">
<insert id="insert" parameterType="Te" useGeneratedKeys="true"
keyProperty="id">
insert into `te` (name) values (#{name})
</insert>
<insert id="batchinsert" parameterType="java.util.List">
insert into `te` (name)
VALUES
<foreach collection="list" item="c" index="index" separator=",">
(#{c.name})
</foreach>
</insert>
</mapper>
测试时batchinsert效率要远高于insert(insert中rewriteBatchedStatements没有生效)。