top / index / prev / next / target / source

2005-11-16 diary: blancoDb Enterprise Edition と 生JDBC APIとの速度比較

いがぴょんの日記 日記形式でつづる いがぴょんコラム ウェブページです。

old-v2

blancoDb Enterprise Edition と 生JDBC APIとの速度比較

blancoDb と 生JDBC APIとの間で速度比較を行ってみました。

blancoDb Enterprise Edition 1.3.0にバグ発見 (苦)

つい先日 β版として公開させていただいた blancoDbに早速バグが見つかりました。

このなかの「コンパイルエラーが発生」のくだりのところについて、原因は 低水準で共通化して利用しているライブラリ (blancoCommons)の名前変形ルーチンにバグが入っていたことでした。アンダーバーを含まない大文字だけで構成された列名があるばあいに、名前変形がうまく働いてくれないメソッドがありました。(さらに問題なことはそれら名前変換メソッドが2個実装されていたことです) 次回リリース (1.4.0系)に向けて 名前変形に関して大幅な整理をおこなったのですが、その過程でバグを作り込んでしまいました。加えて名前変形ルーチンの試験観点が足りないことを反省しました。試験観点を見直します。※なお、このバグは blancoDb 1.2.0には含まれません。

バグを報告していただき、どうもありがとうございました。また、お手をわずらわせてしまい、すみませんでした。

このバグを修正した版をアップロードしておきました。加えて MySQL 5.0仮対応をピンポイントで追加しました。(blancoDbプラグインにおいて JDBCドライバのクラス名がドロップダウンで選択可能になりました+α) ただし、依然として MySQLに対する包括的な試験は未実施です。

blancoDb Enterprise Edition と 生JDBC APIとの速度比較

同じく、[replace-link:blancoDb: Enterprise Edition と 生JDBC APIとの間での性能の差異についてご指摘を頂いたので、私の方でも比較を行ってみました。※そもそも blancoDbは 生JDBC APIコーディングを単に肩代わりするためのツールであるはずなので、blancoDbと生JDBC APIとの間で性能差がでるのだとしたら ゆゆしき問題なので、すぐに確認しました (苦笑)

ターゲットは MySQL 5.0.xベースとしました。

実行結果の比較表 件数生JDBC APIblancoDbblancoDb (PreparedStatement破棄版)100件 1件あたり0.47ミリ秒 1件あたり0.31ミリ秒 1件あたり0.62ミリ秒 1件あたり0.47ミリ秒 1件あたり1.41ミリ秒 1件あたり1.25ミリ秒 1000件 1件あたり0.36ミリ秒 1件あたり0.343ミリ秒 1件あたり0.344ミリ秒 1件あたり0.328ミリ秒 1件あたり1.047ミリ秒 1件あたり0.813ミリ秒 10000件 1件あたり0.1984ミリ秒 1件あたり0.2062ミリ秒 1件あたり0.1969ミリ秒 1件あたり0.2ミリ秒 1件あたり0.7141ミリ秒 1件あたり0.6391ミリ秒

この実行結果に対する私なりの分析は 下記のような感じです。

blancoDb は 良くも悪くも 生JDBC APIを用いたJavaソースコードをツールとして自動生成するだけのツールであるので、生JDBC APIより低速になることも、あるいは高速になることも無いと考えています。とはいえ、私が書いた検証用の Javaソースコードに私の無意識のうちにバイアスがかかっている可能性がありますね… (苦笑) 明日、いまいちど検証用コードの他人によるソースコードレビューを依頼してみます。

実行速度比較に用いた手順やソースコードを下記に示します。

前準備 作表DDL

CREATE
    TABLE
        customer (
            ID INT NOT NULL
            ,NAME VARCHAR (16) NULL
            ,PRIMARY KEY (ID)
        )
;

生JDBC API版

生JDBC API版を用いた速度計測用ソースコードはこちらです。 MyTestJdbc.java

/**
 * 実測値<br>
 *【100件】<br>
 * 生JDBC版: 100件追加:トータル 47ミリ秒, 1件あたり0.47ミリ秒<br>
 * 生JDBC版: 100件削除:トータル 31ミリ秒, 1件あたり0.31ミリ秒<br>
 *
 *【1000件】<br>
 * 生JDBC版: 1000件追加:トータル 360ミリ秒, 1件あたり0.36ミリ秒<br>
 * 生JDBC版: 1000件削除:トータル 343ミリ秒, 1件あたり0.343ミリ秒<br>
 *
 *【10000件】<br>
 * 生JDBC版: 10000件追加:トータル 1984ミリ秒, 1件あたり0.1984ミリ秒<br>
 * 生JDBC版: 10000件削除:トータル 2062ミリ秒, 1件あたり0.2062ミリ秒<br>
 */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.SQLException;

/**
 * PreparedStatementが有効に利用するケースのパターン
 */
public class MyTestJdbc {
    /**
     * 繰り返し回数
     */
    private static final int COUNT = 100;

    /**
     * 自動コミットを行うかどうか
     */
    private static final boolean AUTOCOMMIT = false;

    public static void main(String[] args) {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/test?useUnicode=true", "root",
                    "password");
            if (AUTOCOMMIT == false) {
                // 手動コミットに切り替えます。
                conn.setAutoCommit(false);
            }

            if (true) {
                // 表にINSERTを行います。
                insertInto(conn);
            }

            if (true) {
                // 表からDELETEを行います。
                deleteFrom(conn);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * データをテーブルに挿入
     * 
     * @param conn
     * @throws SQLException
     */
    private static final void insertInto(final Connection conn)
            throws SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でPreparedStatementのインスタンスを生成します。
        final PreparedStatement stmt = conn
                .prepareStatement("INSERT\n  INTO customer\n       (ID, NAME)\nVALUES\n       (?, ?)");
        try {
            for (int index = 0; index < COUNT; index++) {
                final String name = "山田" + String.valueOf(index) + "朗";
                // SQL入力パラメーターをセットします。
                stmt.setInt(1, index);
                stmt.setString(2, name);
                if (stmt.executeUpdate() != 1) {
                    throw new SQLException("追加に失敗しました。");
                }
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            stmt.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("生JDBC版: " + COUNT + "件追加:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }

    /**
     * データをテーブルから削除
     * 
     * @param conn
     * @throws SQLException
     */
    private static final void deleteFrom(final Connection conn)
            throws SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でPreparedStatementのインスタンスを生成します。
        final PreparedStatement stmt = conn
                .prepareStatement("DELETE FROM customer\n WHERE ID = ?");

        try {
            for (int index = 0; index < COUNT; index++) {
                // SQL入力パラメーターをセットします。
                stmt.setInt(1, index);
                if (stmt.executeUpdate() != 1) {
                    throw new SQLException("削除に失敗しました。");
                }
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            stmt.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("生JDBC版: " + COUNT + "件削除:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }
}

blancoDb版

blancoDbが生成したソースコードを用いた速度計測用ソースコードはこちらです。

/**
 * 実測値<br>
 *【100件】<br>
 * Prepared版: 100件追加:トータル 62ミリ秒, 1件あたり0.62ミリ秒<br>
 * Prepared版: 100件削除:トータル 47ミリ秒, 1件あたり0.47ミリ秒<br>
 *
 *【1000件】<br>
 * Prepared版: 1000件追加:トータル 344ミリ秒, 1件あたり0.344ミリ秒<br>
 * Prepared版: 1000件削除:トータル 328ミリ秒, 1件あたり0.328ミリ秒<br>
 *
 *【10000件】<br>
 * Prepared版: 10000件追加:トータル 1969ミリ秒, 1件あたり0.1969ミリ秒<br>
 * Prepared版: 10000件削除:トータル 2000ミリ秒, 1件あたり0.2ミリ秒<br>
 */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import myapp.db.exception.IntegrityConstraintException;
import myapp.db.query.SimpleCustomerDeleteInvoker;
import myapp.db.query.SimpleCustomerInsertInvoker;

/**
 * PreparedStatementが有効に利用するケースのパターン
 */
public class MyTestPrepare {
    /**
     * 繰り返し回数
     */
    private static final int COUNT = 100;

    /**
     * 自動コミットを行うかどうか
     */
    private static final boolean AUTOCOMMIT = false;

    public static void main(String[] args) {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/test?useUnicode=true", "root",
                    "password");
            if (AUTOCOMMIT == false) {
                // 手動コミットに切り替えます。
                conn.setAutoCommit(false);
            }

            if (true) {
                // 表にINSERTを行います。
                insertInto(conn);
            }

            if (true) {
                // 表からDELETEを行います。
                deleteFrom(conn);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * データをテーブルに挿入
     * 
     * @param conn
     * @throws IntegrityConstraintException
     * @throws SQLException
     */
    private static final void insertInto(final Connection conn)
            throws IntegrityConstraintException, SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でinvokerのインスタンスを生成します。
        final SimpleCustomerInsertInvoker invoker = new SimpleCustomerInsertInvoker(
                conn);
        try {
            for (int index = 0; index < COUNT; index++) {
                final String name = "山田" + String.valueOf(index) + "朗";
                // SQL入力パラメーターをセットします。
                invoker.setInputParameter(index, name);
                invoker.executeSingleUpdate();
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            invoker.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("Prepared版: " + COUNT + "件追加:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }

    /**
     * データをテーブルから削除
     * 
     * @param conn
     * @throws IntegrityConstraintException
     * @throws SQLException
     */
    private static final void deleteFrom(final Connection conn)
            throws IntegrityConstraintException, SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でinvokerのインスタンスを生成します。
        final SimpleCustomerDeleteInvoker invoker = new SimpleCustomerDeleteInvoker(
                conn);
        try {
            for (int index = 0; index < COUNT; index++) {
                // SQL入力パラメーターをセットします。
                invoker.setInputParameter(index);
                invoker.executeSingleUpdate();
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            invoker.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("Prepared版: " + COUNT + "件削除:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }
}

blancoDb版 (わざとPreparedStatementを破棄する版)

blancoDbが生成したソースコードを用いた速度計測用ソースコード (わざと性能劣化を発生させるようなPreparedStatementの破棄をおこなっています)

/**
 * 実測値<br>
 *【100件】<br>
 * 非Prepared版: 100件追加:トータル 141ミリ秒, 1件あたり1.41ミリ秒<br>
 * 非Prepared版: 100件削除:トータル 125ミリ秒, 1件あたり1.25ミリ秒<br>
 *
 *【1000件】<br>
 * 非Prepared版: 1000件追加:トータル 1047ミリ秒, 1件あたり1.047ミリ秒<br>
 * 非Prepared版: 1000件削除:トータル 813ミリ秒, 1件あたり0.813ミリ秒<br>
 *
 *【10000件】<br>
 * 非Prepared版: 10000件追加:トータル 7141ミリ秒, 1件あたり0.7141ミリ秒<br>
 * 非Prepared版: 10000件削除:トータル 6391ミリ秒, 1件あたり0.6391ミリ秒<br>
 */
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

import myapp.db.exception.IntegrityConstraintException;
import myapp.db.query.SimpleCustomerDeleteInvoker;
import myapp.db.query.SimpleCustomerInsertInvoker;

/**
 * PreparedStatementが有効に機能しないようにわざと設定したケースのパターン
 */
public class MyTestNonPrepare {
    /**
     * 繰り返し回数
     */
    private static final int COUNT = 10000;

    /**
     * 自動コミットを行うかどうか
     */
    private static final boolean AUTOCOMMIT = false;

    public static void main(String[] args) {
        Connection conn = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            conn = DriverManager.getConnection(
                    "jdbc:mysql://localhost:3306/test?useUnicode=true", "root",
                    "password");
            if (AUTOCOMMIT == false) {
                // 手動コミットに切り替えます。
                conn.setAutoCommit(false);
            }

            if (true) {
                // 表にINSERTを行います。
                insertInto(conn);
            }

            if (true) {
                // 表からDELETEを行います。
                deleteFrom(conn);
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            if (conn != null) {
                try {
                    conn.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * データをテーブルに挿入
     * 
     * @param conn
     * @throws IntegrityConstraintException
     * @throws SQLException
     */
    private static final void insertInto(final Connection conn)
            throws IntegrityConstraintException, SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でinvokerのインスタンスを生成します。
        final SimpleCustomerInsertInvoker invoker = new SimpleCustomerInsertInvoker(
                conn);
        try {
            for (int index = 0; index < COUNT; index++) {
                final String name = "山田" + String.valueOf(index) + "朗";
                // SQL入力パラメーターをセットします。
                invoker.setInputParameter(index, name);
                invoker.executeSingleUpdate();

                // PreparedStatementをわざと破棄して解放するためにclose()を呼び出し
                invoker.close();
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            invoker.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("非Prepared版: " + COUNT + "件追加:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }

    /**
     * データをテーブルから削除
     * 
     * @param conn
     * @throws IntegrityConstraintException
     * @throws SQLException
     */
    private static final void deleteFrom(final Connection conn)
            throws IntegrityConstraintException, SQLException {
        final long start = System.currentTimeMillis();

        // ループの外側でinvokerのインスタンスを生成します。
        final SimpleCustomerDeleteInvoker invoker = new SimpleCustomerDeleteInvoker(
                conn);
        try {
            for (int index = 0; index < COUNT; index++) {
                // SQL入力パラメーターをセットします。
                invoker.setInputParameter(index);
                invoker.executeSingleUpdate();

                // PreparedStatementをわざと破棄して解放するためにclose()を呼び出し
                invoker.close();
            }
            if (AUTOCOMMIT == false) {
                conn.commit();
            }
        } finally {
            // 使い終わったら最後にcloseします。
            invoker.close();
        }

        final long period = System.currentTimeMillis() - start;
        System.out.println("非Prepared版: " + COUNT + "件削除:トータル " + period
                + "ミリ秒, 1件あたり" + ((double) period) / COUNT + "ミリ秒");
    }
}

世間のリリース


この日記について