計測結果についての補足
計測結果はあくまでも私が必要な環境と条件の計測結果であり、id:matsumoto_r さんがおっしゃられているとおりデータベースそのものの優劣を示しているものではありません。並行アクセスしたらとか、設定変えたらとか、たくさんご意見いただきましたが、もちろん、負荷条件を変えたりそれぞれ環境に合わせてチューニングすれば結果は変わると思います。今回の想定システムでは並行アクセスはほぼ発生せず意味がないのでマルチスレッドでの計測はせず、シングルスレッドでのデータ永続化の基本性能を確認するために実施しました。並行処理が得意(前提)とされるデータベースをシングル構成で計測に追加したのは差を確認するためです。並行性能の情報はたくさん公開されていると思いますが、ご自分の環境に合う方法で計測していただければと思います。
なお、ソフトウェアやドライバはすべて最新安定版、設定はデフォルトです。ただし、全データのディスク同期が前提の計測であるため、EHCache はインメモリではなくディスク永続化モードです。ちなみに SQLite はもう少し触ってみたところ Beta 版にすると 20% 高速化、非同期モードにするとさらに 10% 高速化しました。
計測になぜ Oracle が含まれてないの?
私は臆病者なので良い結果を出さないと Oracle に怒られる気がするからです。OTN ライセンスに下記の条項が含まれています。
オラクルの事前承諾なく、プログラムのベンチマークテストの結果を開示すること。
OTN開発者ライセンス
蛇足ですが、DB2 は設定を完璧にして最新パッチをあてて最高性能を出さないと怒られるかもしれません。
(A) ベンチマーク・テストで使用した方法 (例えば、ハードウェアおよびソフトウェアのセットアップ、導入手順および構成ファイル) を公開し、
(B) 「プログラム」に対する IBM または IBM 製品を提供する第三者 (以下「第三者」といいます。) から提供される最新の適用可能な更新、パッチ、修正が適用された所定の稼働環境で 「プログラム」を実行してベンチマーク・テストを行い
(C) 「プログラム」の資料ならびに IBM がサポートする「プログラム」用の Web サイトで提供されているすべてのパフォーマンス・チューニングおよび最良の方法に従うこと
SLA - L-JWOG-6K4JSQ
計測ソース*8
今回の計測対象のメインとなる JDBC のソースはこちらになります。INSERT のあとの SELECT なのでキャッシュ云々の話もありますが、それも含めて各データソースに対して同じ操作をしています。SQLite のみ Class.forName しているのはドライバがサービスプロバイダーフレームワークに対応していないためです。(bitbucket リポジトリの最新ソースでは 2012/09 対応)
package test;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.junit.After;
import org.junit.Test;
public class JdbcTest {
@Test
public void sqlite() throws Exception {
Class.forName("org.sqlite.JDBC");
con = DriverManager.getConnection("jdbc:sqlite:test.sqlite3");
Statement st = con.createStatement();
executeUpdate(st, "drop table if exists person");
executeUpdate(st, "create table person (id integer primary key, name string)");
executeQuery();
}
@Test
public void h2() throws Exception {
con = DriverManager.getConnection("jdbc:h2:testh2", "sa", "");
Statement st = con.createStatement();
executeUpdate(st, "drop table if exists person");
executeUpdate(st, "create table person (id integer primary key, name varchar)");
executeQuery();
}
@Test
public void derby() throws Exception {
con = DriverManager.getConnection("jdbc:derby:derby;create=true");
Statement st = con.createStatement();
executeUpdate(st, "drop table person");
executeUpdate(st, "create table person (id int primary key, name varchar(200))");
executeQuery();
}
@Test
public void mysql_myisam() throws Exception {
mysql("MyISAM");
}
@Test
public void mysql_innodb() throws Exception {
mysql("InnoDB");
}
private void mysql(String engine) throws Exception {
con = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "");
Statement st = con.createStatement();
executeUpdate(st, "drop table if exists person");
executeUpdate(st, "create table person (id integer primary key, name varchar(200)) engine = " + engine);
executeQuery(con.getMetaData().getDatabaseProductName() + "(" + engine + ")");
}
@Test
public void postgres() throws Exception {
con = DriverManager.getConnection("jdbc:postgresql:postgres", "postgres", "postgres");
Statement st = con.createStatement();
executeUpdate(st, "drop table if exists person");
executeUpdate(st, "create table person (id integer primary key, name varchar)");
executeQuery();
}
@Test
public void cassandra() throws Exception {
con = DriverManager.getConnection("jdbc:cassandra://localhost:9160/test");
Statement st = con.createStatement();
executeUpdate(st, "drop table person");
executeUpdate(st, "create table person (id int primary key, name text)");
executeQuery();
}
private Connection con;
private static final int COUNT = 10000 * 10;
private static final String DATA = "12345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890";
@After
public void after() throws Exception {
if (con != null) {
con.close();
}
}
private void executeUpdate(Statement st, String sql) {
try {
st.executeUpdate(sql);
} catch (Exception e) {
System.out.println(e.toString());
}
}
private void executeQuery() throws Exception {
executeQuery(con.getMetaData().getDatabaseProductName());
}
private void executeQuery(String databaseName) throws Exception {
boolean isCassandra = databaseName.contains("Cassandra");
boolean isAutoCommit = isCassandra;
System.out.printf("%-14s", databaseName);
if (!isAutoCommit) {
con.setAutoCommit(false);
}
long insertStart = System.currentTimeMillis();
PreparedStatement insertPs = con.prepareStatement("insert into person (id, name) values(?, '" + DATA + "')");
for (int i = 0; i < COUNT; i++) {
insertPs.setInt(1, i);
insertPs.executeUpdate();
if (!isAutoCommit && i % 10000 == 0) {
con.commit();
}
}
if (!isAutoCommit) {
con.commit();
}
double insertSec = (double) (System.currentTimeMillis() - insertStart) / 1000;
long selectStart = System.currentTimeMillis();
PreparedStatement selectPs = con.prepareStatement("select * from person where id = ?");
for (int i = 0; i < COUNT; i++) {
selectPs.setInt(1, i);
selectPs.executeQuery().next();
}
double selectSec = (double) (System.currentTimeMillis() - selectStart) / 1000;
String countSql = "select count(1) from person";
if (isCassandra) {
countSql += " limit 100000000";
}
ResultSet rs = con.createStatement().executeQuery(countSql);
rs.next();
logProcessTime(rs.getInt(1), insertSec, selectSec);
}
private void logProcessTime(long count, double insertSec, double selectSec) {
System.out.printf("%4d万件 ", count / 10000);
System.out.printf("%7.2f秒 ", insertSec);
System.out.printf("%7.2f秒", selectSec);
System.out.println();
}
}