数百万のデータを挿入して最適化

背景

最後に、数百万のデータを含むプロジェクトを見つけました。これは非常に単純な単一テーブルのクエリでしたが、インデックスを追加した後も 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

おすすめ

転載: blog.csdn.net/mofsfely2/article/details/118283519