Oracle上でデータ追加を行う際、1レコードずつ追加するより、複数件まとめて追加することでSQLの実行回数を減らした方が、処理時間を大幅に短くすることができる。
今回は、Oracle上で1件ずつデータを追加した場合と、一定件数(1,000件)毎にまとめてデータを追加した場合それぞれでSQLの実行速度を測定するプログラムを作成してみたので、共有する。
なお、一括インサートするデータ量が多すぎると、バインド変数の数の上限を超えてしまうため、以下のサイトにある「ORA-01745: ホスト/バインド変数名が無効です。」が発生してしまうので、注意が必要である。
https://ameblo.jp/archive-redo-blog/entry-10304607870.html
前提条件
下記記事の実装が完了していること。
また、Oracle上でMyBatisを利用して複数レコードをまとめて追加/更新する処理については、以下の記事を参考にすること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。
なお、上記の赤枠は、前提条件のプログラムから変更したプログラムである。
Mapperインタフェース・Mapper XMLの内容は以下の通りで、1件レコードを追加するSQL・複数件レコードを追加するSQL・その他必要なSQLを用意している。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 | package com.example.demo; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import java.util.List; @Mapper public interface UserDataMapper { /** * ユーザーデータテーブル(user_data)の全データを削除する */ void truncateData(); /** * 指定した1件のユーザーデータテーブル(user_data)のデータを追加する * @param userData ユーザーデータテーブル(user_data)の追加データ */ void insertDataOne(UserData userData); /** * 指定したユーザーデータテーブル(user_data)のデータのリストをまとめて追加する * @param userDataList ユーザーデータテーブル(user_data)の追加データリスト */ void insertDataMulti(@Param("userDataList") List<UserData> userDataList); /** * ユーザーデータテーブル(user_data)のデータ件数を取得する * @return ユーザーデータテーブル(user_data)のデータ件数 */ Long countAll(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.example.demo.UserDataMapper"> <update id="truncateData"> TRUNCATE TABLE USER_DATA </update> <insert id="insertDataOne" parameterType="com.example.demo.UserData"> INSERT INTO USER_DATA ( id , name , birth_year , birth_month , birth_day , sex , memo ) VALUES ( #{id} , #{name} , #{birthY} , #{birthM} , #{birthD} , #{sex} , #{memo,jdbcType=VARCHAR} ) </insert> <insert id="insertDataMulti" parameterType="java.util.List"> INSERT INTO USER_DATA ( id , name , birth_year , birth_month , birth_day , sex , memo ) <foreach collection="userDataList" item="userData" separator="union all"> ( SELECT #{userData.id} , #{userData.name} , #{userData.birthY} , #{userData.birthM} , #{userData.birthD} , #{userData.sex} , #{userData.memo,jdbcType=VARCHAR} FROM DUAL ) </foreach> </insert> <select id="countAll" resultType="long"> SELECT COUNT(id) FROM USER_DATA </select> </mapper> |
また、サービスクラスのサブクラスの内容は以下の通りで、1件ずつデータを追加した場合と一定件数(1,000件)毎にまとめてデータを追加した場合の処理を実行し、それぞれの実行時間を表示するようにしている。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 | package com.example.demo; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.ArrayList; import java.util.List; @Service public class DemoServiceSub { /** * 追加するユーザーデータの数 */ private static final int CNT_USER_DATA = 100000; /** * 一括追加する場合のレコード数 */ private static final int CNT_INSERT_MULTI = 1000; /** * ユーザーデータテーブル(user_data)へアクセスするマッパー */ @Autowired private UserDataMapper userDataMapper; /** * 性能検証を行うための個別サービス */ public void verifyPerformanceEach() { // 追加するユーザーデータリストを生成する List<UserData> userDataList = new ArrayList<>(); for (int i = 0; i < CNT_USER_DATA; i++) { userDataList.add(makeUserData(i)); } // ユーザーデータテーブル(user_data)を全件削除する userDataMapper.truncateData(); System.out.println("--- ユーザーデータテーブル(user_data)にデータを追加する" + "処理を1レコードずつ実施します start. ---"); // 処理前の現在時刻を取得 long startTime = System.currentTimeMillis(); // ユーザーデータテーブル(user_data)にデータを追加する処理を1レコードずつ実施 for (int i = 0; i < CNT_USER_DATA; i++) { userDataMapper.insertDataOne(userDataList.get(i)); } // 処理後の現在時刻を取得 long endTime = System.currentTimeMillis(); System.out.println("処理件数 : " + userDataMapper.countAll()); System.out.println("処理時間:" + (endTime - startTime) + " ms"); System.out.println("--- ユーザーデータテーブル(user_data)にデータを追加する" + "処理を1レコードずつ実施します end. ---"); System.out.println(); // ユーザーデータテーブル(user_data)を全件削除する userDataMapper.truncateData(); // CNT_INSERT_MULTIレコードずつ追加する際のループ数を算出する int cntInsertLoop = (CNT_USER_DATA + CNT_INSERT_MULTI - 1) / CNT_INSERT_MULTI; System.out.println("--- ユーザーデータテーブル(user_data)にデータを追加する処理を" + CNT_INSERT_MULTI + "レコードずつ実施します start. ---"); // 処理前の現在時刻を取得 startTime = System.currentTimeMillis(); // ユーザーデータテーブル(user_data)にデータを追加する処理を // CNT_INSERT_MULTIレコードずつ実施 for (int i = 0; i < cntInsertLoop; i++) { userDataMapper.insertDataMulti( userDataList.subList(CNT_INSERT_MULTI * i , Math.min(CNT_INSERT_MULTI * (i + 1), CNT_USER_DATA))); } // 処理後の現在時刻を取得 endTime = System.currentTimeMillis(); System.out.println("処理件数 : " + userDataMapper.countAll()); System.out.println("処理時間:" + (endTime - startTime) + " ms"); System.out.println("--- ユーザーデータテーブル(user_data)にデータを追加する処理を" + CNT_INSERT_MULTI + "レコードずつ実施します end. ---"); System.out.println(); } /** * 引数のIDをもつユーザーデータを生成する * @param id ID * @return ユーザーデータ */ private UserData makeUserData(int id) { UserData userData = new UserData(); userData.setId(id); userData.setName("テスト プリン" + id); userData.setSex(Integer.toString(id % 2 + 1)); userData.setBirthY(2012); userData.setBirthM(1); userData.setBirthD(15); userData.setMemo("テスト" + id); return userData; } } |
その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-oracle-performance-multi-insert/demo
サンプルプログラムの実行結果
Spring Bootのメインクラス(DemoApplication.java)を実行した結果、コンソールログに出力される内容は以下の通りで、一定件数(1,000件)毎にまとめてデータを追加した方が、SQLが速くなることが確認できる。
要点まとめ
- Oracle上でデータ追加を行う際、1レコードずつ追加するより、複数件まとめて追加することでSQLの実行回数を減らした方が、処理時間を大幅に短くすることができる。