文章插图
基本上明白了这个小伙伴的意思,于是我自己也写了个测试案例,重新整理了今天这篇文章,希望和小伙伴们一起探讨这个问题,也欢迎小伙伴们提出更好的方案 。
1. 思路分析批量插入这个问题,我们用 JDBC 操作,其实就是两种思路吧:
- 用一个 for 循环,把数据一条一条的插入(这种需要开启批处理) 。
- 生成一条插入 sql,类似这种 insert into user(username,address) values('aa','bb'),('cc','dd')... 。
我们从两方面来考虑这个问题:
- 插入 SQL 本身执行的效率 。
- 网络 I/O 。
- 这种方案的优势在于,JDBC 中的 PreparedStatement 有预编译功能,预编译之后会缓存起来,后面的 SQL 执行会比较快并且 JDBC 可以开启批处理,这个批处理执行非常给力 。
- 劣势在于,很多时候我们的 SQL 服务器和应用服务器可能并不是同一台,所以必须要考虑网络 IO,如果网络 IO 比较费时间的话,那么可能会拖慢 SQL 执行的速度 。
- 这种方案的优势在于只有一次网络 IO,即使分片处理也只是数次网络 IO,所以这种方案不会在网络 IO 上花费太多时间 。
- 当然这种方案有好几个劣势,一是 SQL 太长了,甚至可能需要分片后批量处理;二是无法充分发挥 PreparedStatement 预编译的优势,SQL 要重新解析且无法复用;三是最终生成的 SQL 太长了,数据库管理器解析这么长的 SQL 也需要时间 。
2. 数据测试【10万条数据批量插入,到底怎么做才快?】接下来我们来做一个简单的测试,批量插入 5 万条数据看下 。
首先准备一个简单的测试表:
CREATE TABLE `user` (`id` int(11) unsigned NOT NULL AUTO_INCREMENT,`username` varchar(255) DEFAULT NULL,`address` varchar(255) DEFAULT NULL,`password` varchar(255) DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
接下来创建一个 Spring Boot 工程,引入 MyBatis 依赖和 MySQL 驱动,然后 Application.properties 中配置一下数据库连接信息:spring.datasource.username=rootspring.datasource.password=123spring.datasource.url=jdbc:mysql:///batch_insert?serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true
大家需要注意,这个数据库连接 URL 地址中多了一个参数 rewriteBatchedStatements ,这是核心 。MySQL JDBC 驱动在默认情况下会无视 executeBatch() 语句,把我们期望批量执行的一组 sql 语句拆散,一条一条地发给 MySQL 数据库,批量插入实际上是单条插入,直接造成较低的性能 。将 rewriteBatchedStatements 参数值为 true , 数据库驱动才会帮我们批量执行 SQL 。
OK,这样准备工作就做好了 。
2.1 方案一测试首先我们来看方案一的测试,即一条一条的插入(实际上是批处理) 。
首先创建相应的 mapper,如下:
@Mapperpublic interface UserMapper {Integer addUserOneByOne(User user);}
对应的 XML 文件如下:<insert id="addUserOneByOne">insert into user (username,address,password) values (#{username},#{address},#{password})</insert>
service 如下:@Servicepublic class UserService extends ServiceImpl<UserMapper, User> implements IUserService {private static final Logger logger = LoggerFactory.getLogger(UserService.class);@AutowiredUserMapper userMapper;@AutowiredSqlSessionFactory sqlSessionFactory;@Transactional(rollbackFor = Exception.class)public void addUserOneByOne(List<User> users) {SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);UserMapper um = session.getMapper(UserMapper.class);long startTime = System.currentTimeMillis();for (User user : users) {um.addUserOneByOne(user);}session.commit();long endTime = System.currentTimeMillis();logger.info("一条条插入 SQL 耗费时间 {}", (endTime - startTime));}}
这里我要说一下:虽然是一条一条的插入,但是我们要开启批处理模式(BATCH),这样前前后后就只用这一个 SqlSession,如果不采用批处理模式,反反复复的获取 Connection 以及释放 Connection 会耗费大量时间,效率奇低,这种效率奇低的方式松哥就不给大家测试了 。
推荐阅读
- Oracle不同数据库之间同步处理方案
- 如何不删除数据合并分区?
- 电脑误删文件恢复?用数据恢复软件解决
- 使用IDEA连接ClickHouse OLAP数据库
- 电脑里批量修改文件名,批处理实现及详细说明,findstr补充说明
- 使用MySQL导入数据时出现乱码的两种解决方法
- TD 免费在线的数据库表设计工具
- 大数据时代数据是如何传输的?
- Win11上如何将HTML文件批量转换为PDF,Win11文件转换pdf的方法
- Python深度学习:使用Augly库进行图片数据增强