プログラム悪戦苦闘日記

はてなダイアリーからの移行(遺物)

PreparedStatementは無印Statementより速いのか? part2

 記事の内容を鵜呑みにする前に、自分で測定しようと思った。Webアプリケーションで使われる状況を想定した条件にしたつもりだ。。
 まず、データベース(もどき)としてMS Accessを使った。テーブルレイアウトは次の通りである。

変数名  | 型         |
                                                              • -
ID | character8 | 主キー STR1 | varchar16 | STR2 | varchar16 | STR3 | varchar16 | STR4 | varchar16 | STR5 | varchar16 | STR6 | varchar16 | STR7 | varchar16 | STR8 | varchar16 | TIME | DateTime | 登録時間

インデックスは主キーであるIDのみに張った。Not Null値はIDとTIMEの2つである。データとしては100,000件用意した。データを生成したプログラムは次である。(Rubyで作った)

require 'win32ole'

begin
    app = WIN32OLE.new("ADODB.Connection")
    app.open("Driver={Microsoft Access Driver (*.mdb)};DBQ=C:\\Applications\\Apache Group\\jakarta-struts-1.2.4\\webapps\\sample.mdb")

    1.upto(100_000) { |i|
        id = sprintf("%08d", i)
        sql = "insert into Sample2 (ID, STR1, STR2, STR3, STR4, STR5, STR6, STR7, STR8) " +
            "values ('#{id}', 
                '#{rand(1000).to_s[0...16]}', 
                '#{rand(2000).to_s[0...16]}', 
                '#{rand(3000).to_s[0...16]}', 
                '#{rand(4000).to_s[0...16]}', 
                '#{rand(5000).to_s[0...16]}',
                '#{rand(6000).to_s[0...16]}',
                '#{rand(7000).to_s[0...16]}',
                '#{rand(8000).to_s[0...16]}');"
        app.execute sql
    }
rescue
    STRDRR.puts $!
ensure
    app.close unless app.nil?
end

測定方法は、単に次のSQL文を投げただけである。

"select * from Sample2 where STR1 = '" + random.nextInt(1000) + "';"

条件をひとつ付けて該当するレコードを抽出するSQLである。実際にはwhere条件はもっと複雑であろうが、時間計測のためならこれで十分であろう。このSQLを無印StatementとPreparedStatementの場合で1000回実行した。また、SQLのキャッシュの影響を受けないように(多分MS Accessはキャッシュしないと思うが)無印とPreparedを実行する前に、PCを再起動した。時間計測にはSystem.nanoTime()を使用した。測定したプログラムは次である。

import java.util.*;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;

public class Main {
    static final String url = "jdbc:odbc:Sample";
    static final Random random = new Random();
    static final int MAX = 1000;
    
    public static void main(String[] args) throws Exception {
        Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
        
        final long start = System.nanoTime();
        
        if( "type1".equals(args[0]) ) {
            for(int i=1; i<=MAX; ++i) type1();
        }else {
            for(int i=1; i<=MAX; ++i) type2();
        }
        
        final long end = System.nanoTime();
        
        System.out.println("経過時間:" + ((end - start) / (1000000.0*MAX)) + "msec / 回");
    }
    
    static void type1() throws Exception {
        Connection con = DriverManager.getConnection(url, "", "");
        Statement st = con.createStatement();
        ResultSet rs = st.executeQuery("select * from Sample2 where STR1 = '" + random.nextInt(1000) + "';");
        rs.next();
        rs.close();
        st.close();
        con.close();
    }
    
    static void type2() throws Exception {
        Connection con = DriverManager.getConnection(url, "", "");
        PreparedStatement st = con.prepareStatement("select * from Sample2 where STR1 = ?;");
        st.setInt(1, random.nextInt(1000));
        ResultSet rs = st.executeQuery();
        rs.next();
        rs.close();
        st.close();
        con.close();
    }
}

 
 実行結果は、1000回実行したときの平均値で、

無印Statement:     55.429msec / 回
PreparedStatement: 60.134msec / 回

予想通り、というべきかほとんど同じだった。PreparedStatementを毎回生成するため、結局無印Statementと同じようにSQLのパーズを行っているからだろう。