验证的数据库表结构如下:
CREATE TABLE `t_user` (
`id` int (11 ) NOT NULL AUTO_INCREMENT COMMENT '用户id' ,
`username` varchar (64 ) DEFAULT NULL COMMENT '用户名称' ,
`age` int (4 ) DEFAULT NULL COMMENT '年龄' ,
PRIMARY KEY (`id` )
) ENGINE =InnoDB DEFAULT CHARSET =utf8 COMMENT ='用户信息表' ;
话不多说,开整!
1. 实体类、mapper 和配置文件定义
User 实体
@Data
public class User {
private int id;
private String username;
private int age;
}
mapper 接口 public interface UserMapper {
void batchInsertUser (@Param("list" ) List userList) ;
}
mapper.xml 文件
<insert id ="batchInsertUser" parameterType ="java.util.List" >
insert into t_user(username,age) values
<foreach collection ="list" item ="item" index ="index" separator ="," >
(
#{item.username},
#{item.age}
)
foreach >
insert >
jdbc.properties jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:
jdbc.username=root
jdbc.password=root
sqlMapConfig.xml
<configuration >
<properties resource ="jdbc.properties" > properties >
<typeAliases >
<typeAlias type ="com.zjq.domain.User" alias ="user" > typeAlias >
typeAliases >
<environments default ="developement" >
<environment id ="developement" >
<transactionManager type ="JDBC" > transactionManager >
<dataSource type ="POOLED" >
<property name ="driver" value ="${jdbc.driver}" />
<property name ="url" value ="${jdbc.url}" />
<property name ="username" value ="${jdbc.username}" />
<property name ="password" value ="${jdbc.password}" />
dataSource >
environment >
environments >
<mappers >
<mapper resource ="com/zjq/mapper/UserMapper.xml" > mapper >
mappers >
configuration >
2. 不分批次直接梭哈
MyBatis 直接一次性批量插入 30 万条,代码如下:
@Test
public void testBatchInsertUser ( ) throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession session = sqlSessionFactory.openSession();
System.out .println("===== 开始插入数据 =====" );
long startTime = System.currentTimeMillis();
try {
List userList = new ArrayList<>();
for (int i = 1 ; i <= 300000 ; i++) {
User user = new User();
user.setId(i);
user.setUsername("共饮一杯无 " + i);
user.setAge((int ) (Math.random() * 100 ));
userList.add (user);
}
session.insert("batchInsertUser" , userList);
session.commit();
long spendTime = System.currentTimeMillis()-startTime;
System.out .println("成功插入 30 万条数据,耗时:" +spendTime+"毫秒" );
} finally {
session.close();
}
}
可以看到控制台输出:
Cause: com.mysql.jdbc.PacketTooBigException: Packet for query is too large (27759038 >yun 4194304 ). You can change this value on the server by setting the max_allowed_packet’ variable.
超出最大数据包限制了,可以通过调整 max_allowed_packet 限制来提高可以传输的内容,不过由于 30 万条数据超出太多,这个不可取,梭哈看来是不行了 😅😅😅
既然梭哈不行那我们就一条一条循环着插入行不行呢?
3. 循环逐条插入
mapper 接口和 mapper 文件中新增单个用户新增的内容如下:
void insertUser(User user);
<insert id ="insertUser" parameterType ="user" >
insert into t_user(username,age) values
(
#{username},
#{age}
)
insert >
调整执行代码如下:
@Test
public void testCirculateInsertUser ( ) throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession session = sqlSessionFactory.openSession();
System.out .println("===== 开始插入数据 =====" );
long startTime = System.currentTimeMillis();
try {
for (int i = 1 ; i <= 300000 ; i++) {
User user = new User();
user.setId(i);
user.setUsername("共饮一杯无 " + i);
user.setAge((int ) (Math.random() * 100 ));
session.insert("insertUser" , user);
session.commit();
}
long spendTime = System.currentTimeMillis()-startTime;
System.out .println("成功插入 30 万条数据,耗时:" +spendTime+"毫秒" );
} finally {
session.close();
}
}
执行后可以发现磁盘 IO 占比飙升,一直处于高位。
等啊等等啊等,好久还没执行完。
先不管他了太慢了先搞其他的,等会再来看看结果吧。
控制台输出如下:
总共执行了14909367毫秒,换算出来是4小时八分钟。太慢了。。
还是优化下之前的批处理方案吧
3. MyBatis 实现插入 30 万条数据
先清理表数据,然后优化批处理执行插入:
以下是通过 MyBatis 实现 30 万条数据插入代码实现:
@Test
public void testBatchInsertUser ( ) throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession session = sqlSessionFactory.openSession();
System.out .println("===== 开始插入数据 =====" );
long startTime = System.currentTimeMillis();
int waitTime = 10 ;
try {
List userList = new ArrayList<>();
for (int i = 1 ; i <= 300000 ; i++) {
User user = new User();
user.setId(i);
user.setUsername("共饮一杯无 " + i);
user.setAge((int ) (Math.random() * 100 ));
userList.add (user);
if (i % 1000 == 0 ) {
session.insert("batchInsertUser" , userList);
session.commit();
userList.clear();
Thread.sleep(waitTime * 1000 );
}
}
if (!CollectionUtils.isEmpty(userList)) {
session.insert("batchInsertUser" , userList);
session.commit();
}
long spendTime = System.currentTimeMillis()-startTime;
System.out .println("成功插入 30 万条数据,耗时:" +spendTime+"毫秒" );
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
使用了 MyBatis 的批处理操作,将每 1000 条数据放在一个批次中插入,能够较为有效地提高插入速度。同时请注意在循环插入时要带有合适的等待时间和批处理大小,以防止出现内存占用过高等问题。此外,还需要在配置文件中设置合理的连接池和数据库的参数,以获得更好的性能。
在上面的示例中,我们每插入 1000 行数据就进行一次批处理提交,并等待10秒钟。这有助于控制内存占用,并确保插入操作平稳进行。
五十分钟 执行完毕,时间主要用在了等待上。
如果低谷时期执行,CPU 和磁盘性能又足够的情况下,直接批处理不等待执行:
@Test
public void testBatchInsertUser ( ) throws IOException {
InputStream resourceAsStream =
Resources.getResourceAsStream("sqlMapConfig.xml" );
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
SqlSession session = sqlSessionFactory.openSession();
System.out .println("===== 开始插入数据 =====" );
long startTime = System.currentTimeMillis();
int waitTime = 10 ;
try {
List userList = new ArrayList<>();
for (int i = 1 ; i <= 300000 ; i++) {
User user = new User();
user.setId(i);
user.setUsername("共饮一杯无 " + i);
user.setAge((int ) (Math.random() * 100 ));
userList.add (user);
if (i % 1000 == 0 ) {
session.insert("batchInsertUser" , userList);
session.commit();
userList.clear();
}
}
if (!CollectionUtils.isEmpty(userList)) {
session.insert("batchInsertUser" , userList);
session.commit();
}
long spendTime = System.currentTimeMillis()-startTime;
System.out .println("成功插入 30 万条数据,耗时:" +spendTime+"毫秒" );
} catch (Exception e) {
e.printStackTrace();
} finally {
session.close();
}
}
则 24 秒 可以完成数据插入操作:
可以看到短时CPU和磁盘占用会飙高。
把批处理的量再调大一些调到 5000,在执行:
13 秒 插入成功 30 万条,直接芜湖起飞🛫🛫🛫
4. JDBC 实现插入 30 万条数据
JDBC 循环插入的话跟上面的 MyBatis 逐条插入类似,不再赘述。
以下是 Java 使用 JDBC 批处理实现 30 万条数据插入的示例代码。请注意,该代码仅提供思路,具体实现需根据实际情况进行修改。
@Test
public void testJDBCBatchInsertUser ( ) throws IOException {
Connection connection = null ;
PreparedStatement preparedStatement = null ;
String databaseURL = "jdbc:mysql://localhost:3306/test" ;
String user = "root" ;
String password = "root" ;
try {
connection = DriverManager.getConnection(databaseURL, user, password);
connection.setAutoCommit(false );
System.out .println("===== 开始插入数据 =====" );
long startTime = System.currentTimeMillis();
String sqlInsert = "INSERT INTO t_user ( username, age) VALUES ( ?, ?)" ;
preparedStatement = connection.prepareStatement(sqlInsert);
Random random = new Random();
for (int