複数のスレッドから散発的にイベントを SQLite データベースに記録する Java アプリケーションを作成しました。同時に少数のイベントを生成することで、比較的簡単に SQLite の「データベース ロック」エラーをトリガーできることに気付きました。これにより、最悪のケースの動作を模倣するテスト プログラムを作成するようになり、このユース ケースでの SQLite のパフォーマンスがいかに低く見えるかに驚きました。以下に掲載されているコードは、データベースに 5 つのレコードを追加するだけで、最初は順番に「制御」値を取得します。次に、同じ 5 つのレコードが同時に追加されます。
import java.sql.*;
public class Main {
public static void main(String[] args) throws Exception {
Class.forName("org.sqlite.JDBC");
Connection conn = DriverManager.getConnection("jdbc:sqlite:test.db");
Statement stat = conn.createStatement();
stat.executeUpdate("drop table if exists people");
stat.executeUpdate("create table people (name, occupation)");
conn.close();
SqlTask tasks[] = {
new SqlTask("Gandhi", "politics"),
new SqlTask("Turing", "computers"),
new SqlTask("Picaso", "artist"),
new SqlTask("shakespeare", "writer"),
new SqlTask("tesla", "inventor"),
};
System.out.println("Sequential DB access:");
Thread threads[] = new Thread[tasks.length];
for(int i = 0; i < tasks.length; i++)
threads[i] = new Thread(tasks[i]);
for(int i = 0; i < tasks.length; i++) {
threads[i].start();
threads[i].join();
}
System.out.println("Concurrent DB access:");
for(int i = 0; i < tasks.length; i++)
threads[i] = new Thread(tasks[i]);
for(int i = 0; i < tasks.length; i++)
threads[i].start();
for(int i = 0; i < tasks.length; i++)
threads[i].join();
}
private static class SqlTask implements Runnable {
String name, occupation;
public SqlTask(String name, String occupation) {
this.name = name;
this.occupation = occupation;
}
public void run() {
Connection conn = null;
PreparedStatement prep = null;
long startTime = System.currentTimeMillis();
try {
try {
conn = DriverManager.getConnection("jdbc:sqlite:test.db");
prep = conn.prepareStatement("insert into people values (?, ?)");
prep.setString(1, name);
prep.setString(2, occupation);
prep.executeUpdate();
long duration = System.currentTimeMillis() - startTime;
System.out.println(" SQL Insert completed: " + duration);
}
finally {
if (prep != null) prep.close();
if (conn != null) conn.close();
}
}
catch(SQLException e) {
long duration = System.currentTimeMillis() - startTime;
System.out.print(" SQL Insert failed: " + duration);
System.out.println(" SQLException: " + e);
}
}
}
}
このJavaコードを実行したときの出力は次のとおりです。
[java] Sequential DB access:
[java] SQL Insert completed: 132
[java] SQL Insert completed: 133
[java] SQL Insert completed: 151
[java] SQL Insert completed: 134
[java] SQL Insert completed: 125
[java] Concurrent DB access:
[java] SQL Insert completed: 116
[java] SQL Insert completed: 1117
[java] SQL Insert completed: 2119
[java] SQL Insert failed: 3001 SQLException: java.sql.SQLException: database locked
[java] SQL Insert completed: 3136
5 つのレコードを順番に挿入するには約 750 ミリ秒かかります。同時挿入にはほぼ同じ時間がかかると予想されます。しかし、3 秒のタイムアウトを指定すると、終了すらしないことがわかります。また、SQLite のネイティブ ライブラリ呼び出しを使用して C で同様のテスト プログラムを作成し、同時挿入は同時挿入とほぼ同じ時間で完了しました。問題は私のJavaライブラリにあります。
Cバージョンを実行したときの出力は次のとおりです。
Sequential DB access:
SQL Insert completed: 126 milliseconds
SQL Insert completed: 126 milliseconds
SQL Insert completed: 126 milliseconds
SQL Insert completed: 125 milliseconds
SQL Insert completed: 126 milliseconds
Concurrent DB access:
SQL Insert completed: 117 milliseconds
SQL Insert completed: 294 milliseconds
SQL Insert completed: 461 milliseconds
SQL Insert completed: 662 milliseconds
SQL Insert completed: 862 milliseconds
このコードを 2 つの異なる JDBC ドライバー ( http://www.zentus.com/sqlitejdbcとhttp://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC ) と sqlite4java ラッパーで試しました。毎回同様の結果でした。この動作を持たないJava用のSQLiteライブラリを知っている人はいますか?