component - SQLite в многопоточном Java-приложении



swing html css (2)

Это проблема основной библиотеки SQLite, а не какой-либо оболочки Java. SQLite использует блокировки на основе файловой системы для одновременной синхронизации доступа между процессами, поскольку в качестве встроенной базы данных у нее нет выделенного процесса (сервера) для планирования операций. Поскольку каждый поток в вашем коде создает свое собственное соединение с базой данных, он обрабатывается как отдельный процесс, причем синхронизация происходит через блокировки на основе файлов, которые значительно медленнее, чем любой другой метод синхронизации.

Кроме того, SQLite не поддерживает блокировку строк (пока?). По сути, весь файл базы данных locked для каждой операции. Если вам повезло, и ваша файловая система поддерживает блокировки в байтовом диапазоне, возможно, несколько читателей смогут одновременно получить доступ к вашей базе данных, но вы не должны принимать такое поведение.

Базовая библиотека SQLite по умолчанию позволяет нескольким потокам использовать одно и то же соединение одновременно без проблем. Я предполагаю, что любая нормальная оболочка JDBC допустит такое поведение и в программах на Java, хотя на самом деле я этого не пробовал.

Поэтому у вас есть два решения:

  • Совместно использовать одно и то же соединение JDBC среди всех потоков.

  • Поскольку разработчики SQLite, похоже, думают, что потоки - это зло , было бы лучше, если бы один поток обрабатывал все ваши операции с базой данных и сериализировал задачи БД самостоятельно, используя код Java ...

Возможно, вы захотите взглянуть на этот мой старый вопрос - кажется, в нем накоплено несколько советов по улучшению производительности обновлений в SQLite с течением времени.

https://src-bin.com

Я написал приложение Java, которое время от времени записывает события в базу данных SQLite из нескольких потоков. Я заметил, что могу относительно легко вызывать ошибки SQLite «Database Locked», вызывая небольшое количество событий одновременно. Это заставило меня написать тестовую программу, которая имитирует наихудшее поведение, и я был удивлен тем, насколько плохо работает SQLite в этом случае. Приведенный ниже код просто добавляет пять записей в базу данных, сначала последовательно, чтобы получить «контрольные» значения. Затем те же пять записей добавляются одновременно.

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-секундном перерыве они даже не заканчиваются. Я также написал аналогичную тестовую программу на C, используя вызовы собственной библиотеки SQLite и одновременные вставки заканчивались примерно в то же время, что и параллельные вставки. Так что проблема с моей библиотекой 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

Я попробовал этот код с двумя разными драйверами JDBC ( http://www.zentus.com/sqlitejdbc и http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC ) и оболочкой sqlite4java. Каждый раз результаты были похожи. Кто-нибудь знает библиотеку SQLite для Java, которая не имеет такого поведения?


Answer #1

Я использую одно и то же соединение для нескольких потоков. кроме того, мне пришлось синхронизировать методы db-write, иначе я все еще получаю ошибку bussy





locked