背景
最後に、数百万のデータを含むプロジェクトを見つけました。これは非常に単純な単一テーブルのクエリでしたが、インデックスを追加した後も 20 秒以上かかりました。週末に何百万ものデータクエリの最適化を勉強するしかありません。最初のステップは、数百万のデータを挿入することです。私が最初に使用した方法はストアド プロシージャで、ストアド プロシージャの方が高速で汎用性が高いと考えました。予想外に、2 時間で 500 万件のデータが挿入されました。!!
そこで私はBaiduで記事を読み、分析し、要約し、考え続けました。
最終的に、複数値テーブルの挿入 (値セット 1、値セット 2 の値を挿入)、複数の SQL をまとめて送信するという実装方法が決定されました。
1. 1 つの SQL ステートメントは 10 個の値を綴ります (mysql がサポートできる最長の SQL 長と独自のクエリ フィールドの長さに基づいて、総合的に考慮します。ここでは綴りが少なすぎる可能性があります)。
2. 毎回 10 個の SQL ステートメントを送信します (この数が最も効率的であるという記事もありますが、よくわかりませんが、最も重要なことは効率が要件を満たしていることです)。
3. これは 100 であり、50,000 サイクルが必要です。これは明らかに遅すぎます。上記の作業を完了するにはマルチスレッドが必要です。スレッド数は 16 (ローカル マシンのコア数 8 の 2 倍) に設定されています。 。
4. 各スレッドは平均 3125 個のタスクを処理する必要があります。したがって、キューを使用してタスクを保存することを検討してください (キュー方式の方が効率的です)。
一般に、これは次の 2 つのステップに分けることができます。
最初のステップ: Java プロジェクトのマルチスレッド化とキューのアセンブリで SQL ステートメントを実装します。
ps:
ステップ 2: Alibaba の druid 接続プールは、データベースへの SQL の実行を実装します。
始める
目標は500万データ!
データベース接続を取得する
最初のステップは、データベース接続を取得し、新しい DataSourceUtil ツール クラスを作成する方法を見つけることです。
import com.alibaba.druid.pool.DruidDataSource;
import java.sql.Connection;
/**
* @author: aliyu
* @create: 2021-06-26 23:09
* @description:
*/
public class DataSourceUtil {
private static DruidDataSource druidDataSource;
//初始化数据源
static{
druidDataSource = new DruidDataSource();
//设置数据库连接参数
druidDataSource.setUrl("jdbc:mysql://localhost:3306/db_myframe?serverTimezone=GMT");
druidDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
druidDataSource.setUsername("root");
druidDataSource.setPassword("XXXXX");
//配置初始化大小、最小、最大
druidDataSource.setInitialSize(16);
druidDataSource.setMinIdle(1);
druidDataSource.setMaxActive(16);
//连接泄漏监测
druidDataSource.setRemoveAbandoned(true);
druidDataSource.setRemoveAbandonedTimeout(30);
//配置获取连接等待超时的时间
druidDataSource.setMaxWait(20000);
//配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
druidDataSource.setTimeBetweenEvictionRunsMillis(20000);
//防止过期
druidDataSource.setValidationQuery("SELECT 'x'");
druidDataSource.setTestWhileIdle(true);
druidDataSource.setTestOnBorrow(true);
}
/*一个方法从连接池中获取连接*/
public static Connection getConnect() {
Connection con = null;
try {
con = druidDataSource.getConnection();
} catch (Exception e) {
e.printStackTrace();
}
return con;
}
}
ps: 重要なのは、データ ソース、URL、ユーザー名、パスワードなどの構成ですが
、接続数をコンピューターのコア数の 2 倍の 16 (8*2=16) に構成しました。初期化も16(フル火力)。開放)。
次に、getConnect メソッドが接続を取得します。
スレッドクラスの作成
上記の分析によると、各 SQL グループには 10 個の値があり、10 SQL ごとに 1 つが送信されるため、スレッドは一度に 1 つのタスクを実行することで 100 個の項目を挿入でき、これには 50,000 個のタスクが必要になります。これらの 50,000 のタスクは 16 のスレッドによって完了されます。
public class InsertDataThread implements Runnable {
/**
* 初始化队列长度为50000,16个线程一起完成50000个任务
*/
private static final BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(50000);
/**
* 模拟先插入前天的记录
*/
private static String create_date = "DATE_SUB(current_time(3), INTERVAL 2 day)";
private static String create_time = "DATE_SUB(curdate(), INTERVAL 2 day)";
/**
* 构造方法中为队列初始化赋值
*/
public InsertDataThread() {
for (int j = 0; j < 50000; j++) {
queue.offer(j);
}
}
/**
* 线程任务执行(每次任务插入记录数为10个values乘以 10个sql一起提交 = 100条记录)
*/
@Override
public void run() {
while (true) {
try {
synchronized (this){
//queue作为线程共有的参数,肯定一个时间内只能有一个线程操作
queue.poll(); //取出队头(队列是阻塞的,要一个一个的执行)
if(queue.size() == 33200){
//模拟昨天的数据
create_date = "DATE_SUB(current_time(3), INTERVAL 1 day)";
create_time = "DATE_SUB(curdate(), INTERVAL 1 day)";
}else if(queue.size() == 16600){
//模拟今天的数据
create_date = "current_time(3)";
create_time = "curdate()";
}else if(queue.size() == 0){
break;
}
System.out.println("执行到了第"+ queue.size());
}
/*一个队列插入了100条*/
Connection conn = DataSourceUtil.getConnect();
conn.setAutoCommit(false); //需要关掉自动提交才可以多个sql一起提交
for (int k = 0; k < 10; k++) {
/*一个sql 语句 10 条*/
StringBuffer sb = new StringBuffer();
sb.append("insert into db_myframe.t_authority (authority, authority_type, url, authority_parentid , remark, show_status, hide_children, authority_seq, create_time, create_date) values ");
for (int j = 0; j < 10; j++) {
sb.append("('学生管理', 10, '/mocknnd', 0, '学生管理', 1, 1, 1, "+create_time+", "+create_date+"), ");
}
String sql = sb.toString().substring(0, sb.lastIndexOf(","));
// 最后在所有命令执行完之后再提交事务conn.commit();
PreparedStatement pstmt = conn.prepareStatement(sql);
pstmt.execute();
pstmt.close(); //线程池管理的可能这个不需要自己写了
}
conn.commit(); // 每10个sql提交一次事务
conn.close(); //线程池管理的可能这个不需要自己写了
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
挿入を実行するテストクラスを作成する
public class InsertMillionDataTest {
public static void main(String[] args) {
InsertDataThread insertDataThread = new InsertDataThread();
//核心数的两倍16个线程去执行任务
for (int i = 0; i < 16; i++) {
Thread th1 = new Thread(insertDataThread);
th1.start();
}
}
}
他の
1回100件投稿した場合、実行時間は2分59秒になるようです。
主な参考記事アドレス:https://blog.csdn.net/weixin_39561577/article/details/111257340