2012-05-22 4 views
19

J'ai écrit une application java qui enregistre de manière sporadique des événements dans une base de données SQLite à partir de plusieurs threads. J'ai remarqué que je peux déclencher assez facilement les erreurs SQLite de "Database Locked" en générant un petit nombre d'événements en même temps. Cela m'a conduit à écrire un programme de test qui imite le pire des cas et j'ai été surpris par la façon dont il semble que SQLite se comporte mal dans ce cas d'utilisation. Le code affiché ci-dessous ajoute simplement cinq enregistrements à une base de données, d'abord séquentiellement pour obtenir des valeurs de «contrôle». Ensuite, les mêmes cinq enregistrements sont ajoutés simultanément.SQLite dans une application java multithread

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); 
     } 
     } 
    } 
} 

Voici la sortie quand je lance ce code 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 

Insertion 5 enregistrements séquentiellement prend environ 750 millisecondes, j'attendre les insertions concurrentes à prendre à peu près la même quantité de temps. Mais vous pouvez voir que, compte tenu d'un délai de 3 secondes, ils ne finissent même pas. J'ai également écrit un programme de test similaire en C, en utilisant les appels de bibliothèque natifs de SQLite et les insertions simultanées terminées à peu près en même temps que les insertions simultanées. Donc, le problème est avec ma bibliothèque Java.

est ici la sortie quand je lance la version 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 

J'ai essayé ce code avec deux pilotes JDBC différents (http://www.zentus.com/sqlitejdbc et http://www.xerial.org/trac/Xerial/wiki/SQLiteJDBC), et l'emballage de sqlite4java. Chaque fois que les résultats étaient similaires. Est-ce que quelqu'un sait là-bas une bibliothèque SQLite pour Java qui n'a pas ce comportement?

Répondre

23

Ceci est un problème avec le core SQLite library - pas avec un wrapper Java. SQLite utilise des verrous basés sur le système de fichiers pour la synchronisation des accès simultanés entre les processus, car en tant que base de données intégrée, il n'a pas de processus dédié (serveur) pour planifier les opérations. Comme chaque thread de votre code crée sa propre connexion à la base de données, il est traité comme un processus séparé, la synchronisation s'effectuant via des verrous basés sur des fichiers, qui sont sensiblement plus lents que toute autre méthode de synchronisation.

En outre, SQLite ne prend pas en charge le verrouillage par ligne (pas encore?). Essentiellement, le fichier de base de données entier devient locked pour chaque opération. Si vous avez de la chance et que votre système de fichiers supporte les verrous d'octets, il est possible que plusieurs lecteurs accèdent simultanément à votre base de données, mais vous ne devez pas supposer ce genre de comportement.

La bibliothèque SQLite principale by default allows multiple threads to use the same connection concurrently sans problème. Je suppose que tout wrapper JDBC sain permettra également ce comportement dans les programmes Java, bien que je ne l'ai pas réellement essayé.

Par conséquent, vous avez deux solutions:

  • Partager la même connexion JDBC entre les threads.

  • Depuis les développeurs SQLite semblent penser que threads are evil, vous seriez mieux d'avoir un fil gérer toutes vos opérations de base de données et sérialisation tâches DB sur votre propre code Java en utilisant ...

Vous pouvez jeter un oeil à this old question of mine - il semble avoir accumulé plusieurs conseils sur l'amélioration des performances de mise à jour dans SQLite au fil du temps.J'utilise la même connexion pour plusieurs threads.

+0

Donc, si la racine du problème est les verrous du système de fichiers, pourquoi mon code C tourne-t-il aussi vite? – jlunavtgrad

+2

hourra! L'utilisation de la même connexion JDBC a totalement résolu le problème. Je vois maintenant une performance supérieure ou égale à mon code C. J'avais essayé cela avec le programme Java ci-dessus hier, mais je réalise maintenant que je remplaçais ma connexion avec un nouveau pour chaque insertion. – jlunavtgrad

+0

Voir aussi: http://stackoverflow.com/questions/24513576/opening-database-connection-with-the-sqlite-open-nomutex-flag-in-java – Stephan

1

en plus je devais synchroniser les méthodes db-write, sinon j'ai toujours l'erreur bussy

Questions connexes