Ошибка SQLite «База данных заблокирована» в многопоточном приложении

Есть многопоточное приложение, работающее с большими файлами БД (> 600 Мб). Проблема «База данных заблокирована» началась, когда я добавил данные большого двоичного объекта и начал работать с> 30 КБ данных большого двоичного объекта на запрос. Думаю, проблема связана с небольшой скоростью HDD. Похоже, что SQLite удаляет файл -journal, один поток моего приложения вышел из-под блокировки (потому что файл -journal был применен и удален), а другой мой поток хочет сделать что-то с БД, но SQLite все еще обновляет файл БД ... Конечно , Я могу делать минутные задержки после каждого вызова БД, но это не решение, потому что мне нужно больше скорости.

Теперь я использую реализацию сеанса для каждого разговора (для каждого потока). Таким образом, существует одна ISessionFactory для каждого объекта приложения и множество объектов ISession.

Вот мои вспомогательные классы (как вы можете видеть, я использую IsolationLevel.Serializable и 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();
            }
        }
    }
}

И есть вспомогательный класс репозитория. Как вы можете видеть, при каждом вызове БД есть блокировки, поэтому параллельный вызов БД не может появиться и для разных потоков, потому что объект _locker статичен.

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


}

Я использую подобные предыдущие классы (я вызываю nHibernateHelper.OpenConnection () один раз для каждого потока). Репозиторий создается одиночным тоном:

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

Я пытался изменить IsolationLevel на ReadCommited, но это не меняет проблемы. Также я попытался решить эту проблему, изменив режим журнала SQLite с журнала на WAL:

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

Это помогло на компьютерах с быстрым жестким диском, но на некоторых я получил ту же ошибку. Затем я попытался добавить в репозиторий проверку «Файл обновления БД» и задержку после каждой процедуры сохранения / обновления / удаления:

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

То же решение (проверка файла обновления БД) не работает для файла -journal. Он сообщил, что файл -journal был удален, но у меня все еще были ошибки. Для файла -wal он работает (как я думаю. Мне нужно больше времени, чтобы его протестировать). Но это решение серьезно тормозило программу.

Может, вы мне поможете?

5
задан user809808 22 November 2011 в 13:43
поделиться