web-dev-qa-db-ja.com

マルチスレッドのSQLite Javaアプリケーション

複数のスレッドから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 code:

 [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ライブラリを知っている人はいますか?

21
jlunavtgrad

これは コアSQLiteライブラリ -の問題ですJavaラッパーではありません。SQLiteは組み込みデータベースであるため、プロセス間の同時アクセス同期にファイルシステムベースのロックを使用します操作をスケジュールするための専用プロセス(サーバー)はありません。コード内の各スレッドはデータベースへの独自の接続を作成するため、個別のプロセスとして扱われ、同期はファイルベースのロックを介して行われます。これは、よりも大幅に低速です。その他の同期方法。

さらに、SQLiteは行ごとのロックをサポートしていません(まだ?)。基本的に、データベースファイル全体は操作ごとに locked になります。運が良ければ、ファイルシステムがバイト範囲ロックをサポートしている場合、複数のリーダーがデータベースに同時にアクセスする可能性がありますが、そのような動作を想定しないでください。

コアSQLiteライブラリ デフォルトでは複数のスレッドが同じ接続を同時に使用できます 問題ありません。私は実際に試したことはありませんが、正常なJDBCラッパーであれば、Javaプログラムでもその動作を許可すると思います。

したがって、2つの解決策があります。

  • すべてのスレッド間で同じJDBC接続を共有します。

  • SQLite開発者は スレッドは悪 だと考えているようですので、oneスレッドですべてのデータベース操作を処理する方がよいでしょう。 Javaコード...

私のこの古い質問 -を見てみたいと思うかもしれません-SQLiteの更新パフォーマンスを時間の経過とともに改善するためのいくつかのヒントが蓄積されているようです。

25
thkala

複数のスレッドに同じ接続を使用しています。さらに、db-writeメソッドを同期させる必要がありました。そうしないと、ビジーエラーが発生します。

3
ksbn