SQL SELECT文を記載する際、「SELECT *」よりは「SELECT (カラム名)」を、「SELECT COUNT(*)」よりは「SELECT COUNT((カラム名))」を利用した方が、SQLの実行速度を速くすることができる。
今回は、select文で「*」を利用した場合とカラム名を利用した場合それぞれで、SQLの実行速度を測定するプログラムを作成してみたので、共有する。
なお、SQL文の性能改善を行う方法については、以下のサイトを参照のこと。
https://sites.google.com/site/orapeform/sql_minaoshi
前提条件
下記記事の実装が完了していること。
サンプルプログラムの作成
作成したサンプルプログラムの構成は、以下の通り。

なお、上記の赤枠は、前提条件のプログラムから追加・変更したプログラムである。
Mapperインタフェース・Mapper XMLの内容は以下の通りで、select文で「*」を利用した場合とカラム名を利用した場合それぞれで、全データを取得するSQLと、全データ件数を取得するSQLを用意している。
package com.example.demo;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
@Mapper
public interface UserDataMapper {
/**
* 指定したIDをもつユーザーデータテーブル(user_data)のデータを取得する
* @param id ID
* @return ユーザーデータテーブル(user_data)の指定したIDのデータ
*/
UserData findById(Long id);
/**
* select * によってユーザーデータテーブル(user_data)のデータを全件取得する
* @return ユーザーデータテーブル(user_data)のリスト
*/
List<UserData> findAllByAsta();
/**
* select (カラム名) によってユーザーデータテーブル(user_data)のデータを全件取得する
* @return ユーザーデータテーブル(user_data)のリスト
*/
List<UserData> findAllByColumn();
/**
* select count(*) によってユーザーデータテーブル(user_data)のデータ件数を取得する
* @return ユーザーデータテーブル(user_data)のデータ件数
*/
Long countAllByAsta();
/**
* select count((カラム名)) によってユーザーデータテーブル(user_data)のデータ件数を取得する
* @return ユーザーデータテーブル(user_data)のデータ件数
*/
Long countAllByColumn();
}<?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">
<resultMap id="userDataResultMap" type="com.example.demo.UserData" >
<id column="id" property="id" jdbcType="BIGINT" />
<result column="name" property="name" jdbcType="VARCHAR" />
<result column="birth_year" property="birthY" jdbcType="VARCHAR" />
<result column="birth_month" property="birthM" jdbcType="VARCHAR" />
<result column="birth_day" property="birthD" jdbcType="VARCHAR" />
<result column="sex" property="sex" jdbcType="VARCHAR" />
<result column="memo" property="memo" jdbcType="VARCHAR" />
</resultMap>
<select id="findById" parameterType="java.lang.Long" resultMap="userDataResultMap">
SELECT id, name, birth_year, birth_month, birth_day, sex, memo
FROM USER_DATA WHERE id = #{id}
</select>
<select id="findAllByAsta" resultMap="userDataResultMap">
SELECT * FROM USER_DATA
</select>
<select id="findAllByColumn" resultMap="userDataResultMap">
SELECT id, name, birth_year, birth_month, birth_day, sex, memo FROM USER_DATA
</select>
<select id="countAllByAsta" resultType="long">
SELECT COUNT(*) FROM USER_DATA
</select>
<select id="countAllByColumn" resultType="long">
SELECT COUNT(id) FROM USER_DATA
</select>
</mapper>また、ユーザデータテーブルのエンティティクラスの内容は、以下の通り。
package com.example.demo;
import lombok.Data;
@Data
public class UserData {
/** ID */
private long id;
/** 名前 */
private String name;
/** 生年月日_年 */
private int birthY;
/** 生年月日_月 */
private int birthM;
/** 生年月日_日 */
private int birthD;
/** 性別 */
private String sex;
/** メモ */
private String memo;
}さらに、サービスクラスのサブクラスの内容は以下の通りで、それぞれのSQLを呼び出し、それぞれの実行時間を表示するようにしている。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class DemoServiceSub {
/**
* ユーザーデータテーブル(user_data)へアクセスするマッパー
*/
@Autowired
private UserDataMapper userDataMapper;
/**
* 性能検証を行うための個別サービス
*/
public void verifyPerformanceEach() {
// 初回DB接続時は時間がかかるため、あえて性能測定対象外のSQLを1度実行しておく
userDataMapper.findById(Long.valueOf(1));
System.out.println("--- SELECT * によってユーザーデータテーブル(user_data)の"
+ "データを全件取得するSQLを実行します start. ---");
// 処理前の現在時刻を取得
long startTime = System.currentTimeMillis();
// SELECT * によってユーザーデータテーブル(user_data)のデータを全件取得する
List<UserData> userDataList = userDataMapper.findAllByAsta();
// 処理後の現在時刻を取得
long endTime = System.currentTimeMillis();
System.out.println("取得件数 : " + userDataList.size());
System.out.println("処理時間:" + (endTime - startTime) + " ms");
System.out.println("--- SELECT * によってユーザーデータテーブル(user_data)の"
+ "データを全件取得するSQLを実行します end. ---");
System.out.println();
System.out.println("--- SELECT (カラム名) によってユーザーデータテーブル(user_data)の"
+ "データを全件取得するSQLを実行します start. ---");
// 処理前の現在時刻を取得
startTime = System.currentTimeMillis();
// SELECT (カラム名) によってユーザーデータテーブル(user_data)のデータを全件取得する
userDataList = userDataMapper.findAllByColumn();
// 処理後の現在時刻を取得
endTime = System.currentTimeMillis();
System.out.println("取得件数 : " + userDataList.size());
System.out.println("処理時間:" + (endTime - startTime) + " ms");
System.out.println("--- SELECT (カラム名) によってユーザーデータテーブル(user_data)の"
+ "データを全件取得するSQLを実行します end. ---");
System.out.println();
System.out.println("--- SELECT count(*) によってユーザーデータテーブル(user_data)の"
+ "データ件数を取得するSQLを実行します start. ---");
// 処理前の現在時刻を取得
startTime = System.currentTimeMillis();
// SELECT count(*) によってユーザーデータテーブル(user_data)のデータ件数を取得する
Long cntUserData = userDataMapper.countAllByAsta();
// 処理後の現在時刻を取得
endTime = System.currentTimeMillis();
System.out.println("取得件数 : " + cntUserData);
System.out.println("処理時間:" + (endTime - startTime) + " ms");
System.out.println("--- SELECT count(*) によってユーザーデータテーブル(user_data)の"
+ "データ件数を取得するSQLを実行します end. ---");
System.out.println();
System.out.println("--- SELECT count((カラム名)) によってユーザーデータテーブル"
+ "(user_data)のデータ件数を取得するSQLを実行します start. ---");
// 処理前の現在時刻を取得
startTime = System.currentTimeMillis();
// SELECT count((カラム名)) によってユーザーデータテーブル(user_data)のデータ件数を取得する
cntUserData = userDataMapper.countAllByColumn();
// 処理後の現在時刻を取得
endTime = System.currentTimeMillis();
System.out.println("取得件数 : " + cntUserData);
System.out.println("処理時間:" + (endTime - startTime) + " ms");
System.out.println("--- SELECT count((カラム名)) によってユーザーデータテーブル"
+ "(user_data)のデータ件数を取得するSQLを実行します end. ---");
}
}また、サービスクラスの内容は以下の通りで、性能測定のためのサービスクラスのサブクラスのメソッドを2回呼び出している。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DemoService {
/**
* 性能測定を行うための個別サービス
*/
@Autowired
private DemoServiceSub demoServiceSub;
/**
* 性能検証を行うためのサービス
*/
public void verifyPerformance() {
System.out.println("com.example.demo.DemoService.verifyPerformance start.");
System.out.println();
System.out.println("=== 1回目の性能測定を行います. ===");
demoServiceSub.verifyPerformanceEach();
System.out.println();
System.out.println("=== 2回目の性能測定を行います. ===");
demoServiceSub.verifyPerformanceEach();
System.out.println();
System.out.println("com.example.demo.DemoService.verifyPerformance end.");
}
}さらに、Spring Bootのメインクラスの内容は以下の通りで、サービスクラスのメソッドを呼び出している。
package com.example.demo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
/**
* 性能検証を行うためのサービス
*/
@Autowired
private DemoService demoService;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@Override
public void run(String... args) {
try {
// 性能検証を行うためのサービスを呼び出す
demoService.verifyPerformance();
} catch (Exception ex) {
System.err.println(ex);
}
}
}その他のソースコード内容は、以下のサイトを参照のこと。
https://github.com/purin-it/java/tree/master/spring-boot-oracle-performance-select-all/demo
サンプルプログラムの実行結果
サンプルプログラムの実行結果は以下の通りで、select文で「*」よりカラム名を指定した方が、SQLが速くなることが確認できる。
1) 以下を実行し、USER_DATAテーブルに100,000件のデータを追加する。
begin
execute immediate 'TRUNCATE TABLE user_data';
for i in 1..100000 loop
INSERT INTO user_data VALUES (i, 'テスト プリン' || TO_MULTI_BYTE(i)
, 2012, 1, 20, TO_CHAR(MOD(i, 2) + 1), 'テスト');
end loop;
COMMIT;
end;
/
実行後、以下のように、レコード数が100,000件になっていることが確認できる。


2) 1)の状態でSpring Bootのメインクラス(DemoApplication.java)を実行した結果、コンソールログに出力される内容は以下の通り。
要点まとめ
- SQL SELECT文を記載する際、「SELECT *」よりは「SELECT (カラム名)」を、「SELECT COUNT(*)」よりは「SELECT COUNT((カラム名))」を利用した方が、SQLの実行速度を速くすることができる。






