2011-11-22 6 views
4

Il existe une application multithreads qui fonctionne avec un fichier DB volumineux (> 600 Mo). Le problème "La base de données est verrouillée" a commencé lorsque j'ai ajouté des données BLOB et j'ai commencé à utiliser des données BLOB> 30 Ko par requête. Je pense que le problème lié à la petite vitesse du disque dur. Il ressemble à SQLite supprime -journal fichier, un thread de mon application est sorti de lock (parce que -journal fichier a été appliqué et supprimé), et d'autres mon thread veulent faire smth avec DB, mais SQLite met encore à jour le fichier DB ... Sure , Je peux faire une minute de retard après chaque appel de DB, mais ce n'est pas une solution, parce que j'ai besoin de plus de vitesse. Maintenant, j'utilise une implémentation de session par conversation (par thread). Il y a donc une ISessionFactory par objet d'application et de nombreux objets ISession.Erreur SQLite "Database is locked" dans l'application multithreads

Il y a mes classes d'aide (comme vous pouvez le voir, je l'utilise IsolationLevel.Serializable et CurrentSessionContext = ThreadStaticSessionContext):

public abstract class nHibernateHelper 
{ 
    private static FluentConfiguration _configuration; 
    private static IPersistenceContext _persistenceContext; 

    static nHibernateHelper() {} 

    private static FluentConfiguration ConfigurePersistenceLayer() 
    { 
     return Fluently.Configure().Database(FluentNHibernate.Cfg.Db.SQLiteConfiguration.Standard.ShowSql().UsingFile(_fileName).IsolationLevel(IsolationLevel.Serializable).MaxFetchDepth(2)). 
       Mappings(m => m.FluentMappings.AddFromAssemblyOf<Foo>()).CurrentSessionContext(typeof(ThreadStaticSessionContext).FullName); 
    } 

    public static ISession CurrentSession 
    { 
     get { return _persistenceContext.CurrentSession; } 
    } 

    public static IDisposable OpenConnection() 
    { 
     return new DbSession(_persistenceContext); 
    } 
} 

public class PersistenceContext : IPersistenceContext, IDisposable 
{ 
    private readonly FluentConfiguration _configuration; 
    private readonly ISessionFactory _sessionFactory; 

    public PersistenceContext(FluentConfiguration configuration) 
    { 
     _configuration = configuration; 
     _sessionFactory = _configuration.BuildSessionFactory(); 
    } 

    public FluentConfiguration Configuration { get { return _configuration; } } 
    public ISessionFactory SessionFactory { get { return _sessionFactory; } } 

    public ISession CurrentSession 
    { 
     get 
     { 
      if (!CurrentSessionContext.HasBind(SessionFactory)) 
      { 
       OnContextualSessionIsNotFound(); 
      } 
      var contextualSession = SessionFactory.GetCurrentSession(); 
      if (contextualSession == null) 
      { 
       OnContextualSessionIsNotFound(); 
      } 
      return contextualSession; 
     } 
    } 

    public void Dispose() 
    { 
     SessionFactory.Dispose(); 
    } 

    private static void OnContextualSessionIsNotFound() 
    { 
     throw new InvalidOperationException("Ambient instance of contextual session is not found. Open the db session before."); 
    } 

} 

public class DbSession : IDisposable 
{ 
    private readonly ISessionFactory _sessionFactory; 

    public DbSession(IPersistenceContext persistentContext) 
    { 
     _sessionFactory = persistentContext.SessionFactory; 
     CurrentSessionContext.Bind(_sessionFactory.OpenSession()); 
    } 

    public void Dispose() 
    { 
     var session = CurrentSessionContext.Unbind(_sessionFactory); 
     if (session != null && session.IsOpen) 
     { 
      try 
      { 
       if (session.Transaction != null && session.Transaction.IsActive) 
       { 
        session.Transaction.Rollback(); 
       } 
      } 
      finally 
      { 
       session.Dispose(); 
      } 
     } 
    } 
} 

Et il est classe d'aide référentiel. Comme vous pouvez le voir, il y a des verrous pour chaque appel de base de données, donc l'appel de la base de données de concurrence ne peut pas apparaître, même pour différents threads, car l'objet _locker est statique.

public abstract class BaseEntityRepository<T, TId> : IBaseEntityRepository<T, TId> where T : BaseEntity<TId> 
{ 
    private ITransaction _transaction; 
    protected static readonly object _locker = new object(); 

    public bool Save(T item) 
    { 
     bool result = false; 

     if ((item != null) && (item.IsTransient())) 
     { 
      lock (_locker) 
      { 
       try 
       { 
        _transaction = session.BeginTransaction(); 
        nHibernateHelper.CurrentSession.Save(item); 
        nHibernateHelper.Flush(); 
        _transaction.Commit();   
        result = true; 
       } catch 
       { 
        _transaction.Rollback(); 
        throw; 
       } 
       //DelayAfterProcess(); 
      } 
     } 
     return result; 
    } 

    //same for delete and update 

    public T Get(TId itemId) 
    { 
     T result = default(T); 

     lock (_locker) 
     { 
      try 
      { 
       result = nHibernateHelper.CurrentSession.Get<T>(itemId); 
      } 
      catch 
      { 
       throw; 
      } 
     } 
     return result; 
    } 

    public IList<T> Find(Expression<Func<T, bool>> predicate) 
    { 
     IList<T> result = new List<T>(); 
     lock (_locker) 
     { 
      try 
      { 
       result = nHibernateHelper.CurrentSession.Query<T>().Where(predicate).ToList(); 
      } 
      catch 
      { 
       throw; 
      } 
     } 
     return result; 
    } 


} 

J'utilise les classes précédentes comme ça (je l'appelle nHibernateHelper.OpenConnection() une fois par fil). Repository est instancié par Singletone:

using (nHibernateHelper.OpenConnection()) 
{ 
    Foo foo = new Foo(); 
    FooRepository.Instance.Save(foo); 
}  

J'ai essayé de changer IsolationLevel à ReadCommited, mais cela ne change un problème. Aussi j'ai essayé de résoudre ce problème par le changement mode journal SQLite de journal pour WAL:

using (nHibernateHelper.OpenConnection()) 
{ 
    using (IDbCommand command = nHibernateHelper.CurrentSession.Connection.CreateCommand()) 
    { 
     command.CommandText = "PRAGMA journal_mode=WAL"; 
     command.ExecuteNonQuery(); 
    } 
} 

Cela a permis sur les ordinateurs avec disque dur rapide, mais sur certains je suis même erreur. Alors j'ai essayé d'ajouter « fichier de mise à jour DB existe » vérifiez dépôt et délai après chaque enregistrement/mise à jour/procédure d'effacement:

protected static int _delayAfterInSeconds = 1; 
    protected void DelayAfterProcess() 
    { 
     bool dbUpdateInProcess = false; 
     do 
     { 
      string fileMask = "*-wal*"; 
      string[] files = Directory.GetFiles(Directory.GetCurrentDirectory(), fileMask); 
      if ((files != null) && (files.Length > 0)) 
      { 
       dbUpdateInProcess = true; 
       Thread.Sleep(1000); 
      } 
      else 
      { 
       dbUpdateInProcess = false; 
      } 
     } while (dbUpdateInProcess); 
     if (_delayAfterInSeconds > 0) 
     { 
      Thread.Sleep(_delayAfterInSeconds * 1000); 
     } 
    } 

Même solution (vérifier le fichier de mise à jour DB) pas travaillé pour le fichier -journal. Il a signalé que le fichier journal a été supprimé, mais j'ai toujours des erreurs. Pour le fichier -wal cela fonctionne (comme je pense, j'ai besoin de plus de temps pour le tester). Mais cette solution sérieusement frein le programme.

Peut-être que vous pouvez m'aider?

Répondre

3

Répondre à moi-même. Le problème était lié à .IsolationLevel (IsolationLevel Serializable). Lorsque j'ai changé cette ligne à .IsolationLevel (IsolationLevel readCommitted) problème a disparu.

0

sqlite est conçu pour être comme ce "verrouillage" d'où la lite dans le nom. Il est conçu pour la seule connexion client. Mais ce que vous pourriez faire utiliser plus d'un fichier de base de données pour les différentes zones de votre application, ce qui peut-être caler le problème jusqu'à ce que votre base de données se développe à nouveau.

+0

Je sais ce que SQLite aime. C'est pourquoi j'ai utilisé le mécanisme 'lock() {}' pour éviter les connexions de concurrence. Si je comprends bien, tout ** doit ** fonctionner comme ceci: 'mon code {verrou {code SQLite}}; mon code {lock {code SQLite}}; ... 'Le travail de code SQLite doit finir à l'intérieur du bloc de verrouillage. Mais SQLite a signalé, qu'il a fini (contrôle de retour à mon application)!Donc, l'application gauche bloc de verrouillage et a commencé un autre. Mais SQLite continue à fonctionner. SQLite trompe mon application sur son état. – user809808

+0

Il pourrait y avoir un léger décalage entre sqlite pensant qu'il est prêt et le système de fichiers ayant fini avec elle. Est-ce que le fichier se déverrouille éventuellement après un certain temps? ou avez-vous d'autres tentatives d'accès pour le garder dans une serrure? –

+0

Si vous voulez dire que le fichier -journal disparaît après un certain temps, non, ce n'est pas le cas. Je vois toujours -journal fichier vide (0 octets) après n'importe quelle période de temps. Et oui, j'ai essayé de répéter l'appel de Session.BeginTransaction échoué après un certain temps mais j'ai eu la même erreur: _Begin a échoué avec l'exception SQL. Le fichier de base de données est verrouillé \ r \ ndatabase est verrouillé_ – user809808

0

Vérifiez-vous que la base de données a été utilisée sur un autre thread avant d'insérer des lignes?

+0

. J'ai vérifié cela par le bloc lock {} dans le code. Donc, je suis pleinement convaincu de DB libre d'autres discussions. Et oui, j'ai vérifié qu'avant que le problème ne se produise dans le thread X, DB est utilisé dans le thread Y. – user809808