2012-05-22 3 views
19

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

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

ответ

23

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

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

Основная библиотека SQLite by default allows multiple threads to use the same connection concurrently без проблем. Я полагаю, что любая нормальная JDBC-оболочка позволит это поведение в программах Java, хотя я и не пробовал это.

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

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

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

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

+0

Итак, если корень проблемы - блокировки файловой системы, почему мой код C работает так же быстро, как и он? – jlunavtgrad

+2

ура! Использование того же JDBC-соединения полностью решило проблему. Теперь я вижу производительность лучше или равна моему коду C. Вчера я пробовал это с помощью вышеупомянутой java-программы, но теперь понимаю, что я переписываю свое соединение с новым для каждой вставки. – jlunavtgrad

+0

См. Также: http://stackoverflow.com/questions/24513576/opening-database-connection-with-the-sqlite-open-nomutex-flag-in-java – Stephan

1

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

Смежные вопросы