SQLite «База данных заблокирована» ошибка в многопоточном приложении - PullRequest
5 голосов
/ 22 ноября 2011

Существует многопоточное приложение, которое работает с большим файлом БД (> 600 Мб). Проблема «База данных заблокирована» началась, когда я добавил данные BLOB-объектов, и начал обрабатывать более 30 Кбайт данных BLOB на запрос. Я думаю, что проблема связана с небольшой скоростью жесткого диска. Похоже, 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();
    }
}

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

    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. Он сообщил, что файл журнала был удален, но я все еще получил ошибки. Для файла -wal это работает (как я думаю. Мне нужно больше времени, чтобы проверить это). Но это решение серьезно тормозит программу.

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

Ответы [ 3 ]

4 голосов
/ 22 июня 2012

Отвечая самому себе. Проблема была связана с .IsolationLevel (IsolationLevel. Сериализуемый ). Когда я изменил эту строку на .IsolationLevel (IsolationLevel. ReadCommitted ), проблема исчезла.

0 голосов
/ 22 июня 2012

Проверяете ли вы, что база данных использовалась в другом потоке перед вставкой строк?

0 голосов
/ 22 ноября 2011

sqlite спроектирован так, чтобы быть похожим на эту «блокировку», отсюда и название в названии.Он рассчитан только на одно клиентское соединение.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...