プロジェクトのシナリオ:
突然、mbatis の最も速い挿入速度がどれくらいかをテストしたくなったので、実験してみます。
環境整備
mysql をインストールします。mysql のローカル バージョンは 8.0.12、springboot のバージョンは 2.4.4 です。
今回は、挿入テストに druid スレッド プール + スレッド プール + Excutor バッチのモードを使用します。テーブルの挿入、シングル スレッドの方が高速ですが、実際にはそうではありません。テーブルはデータベースの保存操作中にロックされますが、コミット時に競合が発生します。つまり、データ挿入には、SQL 解析 + SQL 最適化などの他の時間のかかる操作、そして最後にテーブルをロックするコミット操作が含まれるため、マルチスレッド挿入を使用した方がシングルスレッドよりも高速です。
次のように DDL でテーブルを作成します
-- test1.a definition
CREATE TABLE `a` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
`name` varchar(255) DEFAULT NULL,
`sex` int(255) DEFAULT NULL,
`age` int(11) DEFAULT NULL,
`insertTime` datetime DEFAULT NULL,
UNIQUE KEY `id` (`id`),
KEY `nameindex` (`name`,`sex`,`age`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=52893 DEFAULT CHARSET=utf8;
一括挿入:
1. mybatis druid 接続プールを使用します。
ドルイド自動組み立ての導入
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.17</version>
</dependency>
ドルイドの設定を追加します。
spring.datasource.druid.initialSize=8
spring.datasource.druid.minIdle=10
spring.datasource.druid.maxActive=50
spring.datasource.druid.maxWait=60000
spring.datasource.druid.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.minEvictableIdleTimeMillis=300000
spring.datasource.druid.validationQuery=SELECT 1 FROM DUAL
spring.datasource.druid.testWhileIdle=true
spring.datasource.druid.testOnBorrow=false
spring.datasource.druid.testOnReturn=false
spring.datasource.druid.poolPreparedStatements=true
spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.useGlobalDataSourceStat=true
spring.datasource.druid.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
構成されたデータベース接続の最大数は 50 です。
2. コード構成スレッドプールクラス
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@Configuration
public class ExcutorPool {
@Bean
public ThreadPoolExecutor threadPoolExecutor() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(20, 50, 5, TimeUnit.SECONDS, new ArrayBlockingQueue<>(100));
executor.allowCoreThreadTimeOut(true);
return executor;
}
}
ここでは、20 個のスレッドを使用して同時書き込みを行っています。
3.書き込みサービス
@Autowired
private SqlSessionTemplate sqlSessionTemplate;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Override
public void insertUser() {
for(int i = 0; i < 20; i++) {
threadPoolExecutor.execute(new RunTask(sqlSessionTemplate));
}
threadPoolExecutor.shutdown();
long t1 = System.currentTimeMillis();
while(true){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(threadPoolExecutor.isTerminated()){
System.out.println("都结束了!耗时:"+ ((System.currentTimeMillis()-t1)/1000)+"s");
break;
}
}
}
20回ループし、20本のスレッドを挿入します。スレッドプールタスクの実行後、印刷は終了します。
3.タスクの実行
package com.example.springdemo.demo.service.impl;
import com.example.springdemo.demo.dao.UserDao;
import com.example.springdemo.demo.model.User;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.SqlSessionTemplate;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
public class RunTask implements Runnable {
private SqlSessionTemplate sqlSessionTemplate;
public RunTask(SqlSessionTemplate sqlSessionTemplate){
this.sqlSessionTemplate= sqlSessionTemplate;
}
@Override
public void run() {
System.out.println("开始执行插入任务:"+Thread.currentThread().getName());
long beginTime = System.currentTimeMillis();
SqlSession session = sqlSessionTemplate.getSqlSessionFactory().openSession(ExecutorType.BATCH,false);
//通过新的session获取mapper
UserDao userDao = session.getMapper(UserDao.class);
int size = 5000;
try{
for(int i = 0; i < size; i++) {
User user = new User();
Date date = new Date();
user.setName("小红"+date.getTime());
user.setAge(12);
user.setSex(1);
user.setInsertTime(date);
userDao.insertUser(user);
}
//手动每1000个一提交,提交后无法回滚
session.commit();
//清理缓存,防止溢出
session.clearCache();
} catch (Exception e) {
//没有提交的数据可以回滚
session.rollback();
} finally{
session.close();
}
System.out.println("thread:"+ Thread.currentThread().getName()+",耗时:"+ ((System.currentTimeMillis()-beginTime)/1000)+"s");
}
}
runtask は特定の保存タスクを実行し、5000 レコードごとにコミット操作を実行します。
また、タスクの実行後、保存された 5000 件のレコードを印刷するのに何秒かかりますか。
4.daoインターフェースとSQL
@Repository
public interface UserDao {
User getUser();
void insertUser(User user);
}
<insert id="insertUser" parameterType="com.example.springdemo.demo.model.User">
insert into a (name,sex,age,insertTime) values (#{
name},#{
sex},#{
age},#{
insertTime});
</insert>
モデルクラス
@Data
public class User {
private Integer id;
@NotBlank
private String name;
private Integer sex;
private Integer age;
private Date insertTime;
}
5. テストを開始する
テスト時にリクエストするgetリクエストを定義したので今回は書きません。ここでは 20 回のサイクルと毎回 5000 回の挿入、つまり 10w のデータ挿入速度テストです。これ以上のデータはテストされず、基本的に 10w で速度を計算できます。
タスクを開始して、
ディスクの書き込み速度を確認します。
最初は書き込み速度が比較的高く、その後は平均約 700kb になりました。
481 秒かかり、1 秒あたり 200 レコードが挿入されます。
要約:
このテストは私自身の実験であり、参照のみを目的としています。具体的な運用環境は大きく異なる可能性があります。結局のところ、テーブル フィールドは私ほど単純ではありません。実は挿入するときにinsert intovaluesの書き込みメソッドを使うこともできるのですが、この書き込み方法だと5000個の項目を結合してエラーが報告されるはずで、SQLが長すぎるので実際の使い方はこのようには書けませんこの場合、私はテストしません。興味のある友達は自分でテストして遊んでください。