SQLite в многопоточном приложении java

Я написал приложение java, которое спорадически регистрирует события в базе данных SQLite из нескольких потоков. Я заметил, что могу относительно легко вызвать ошибки SQLite "база данных заблокирована", порождая небольшое количество событий одновременно. Это заставило меня написать тестовую программу, которая имитирует худшее поведение, и я был удивлен тем, насколько плохо SQLite работает в этом случае использования. Код, размещенный ниже, просто добавляет пять записей в базу данных, сначала последовательно получать значения "control". Затем одновременно добавляются те же пять записей.

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, которая не имеет такого поведения?

2 ответов


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

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

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

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

  • поделитесь тем же соединением JDBC между всеми потоками.

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

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


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