SQLite 在多线程应用程序中出现“数据库已锁定”错误

问题描述 投票:0回答:3

有一个多线程应用程序,可处理大型数据库文件(> 600 Mb)。当我添加 blob 数据并开始对每个请求使用 >30 Kb 的 BLOB 数据进行操作时,“数据库已锁定”问题开始出现。我认为问题与小硬盘速度有关。看起来 SQLite 删除了 -journal 文件,我的应用程序的一个线程失去了锁定(因为 -journal 文件被应用并删除了),而我的其他线程想要对 DB 进行操作,但 SQLite 仍然更新 DB 文件...当然,我可以在每次数据库调用后延迟一分钟,但这不是解决方案,因为我需要更快的速度。

现在我使用每个会话(每个线程)的会话实现。因此,每个应用程序对象有一个 ISessionFactory 和许多 ISession 对象。

有我的帮助器类(如您所见,我使用 IsolationLevel.Serialized 和 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() 一次)。 Repository是通过singletone实例化的:

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

我尝试将 IsolationLevel 更改为 ReadCommited,但这并不能改变问题。我还尝试通过将 SQLite 日志模式从 Journal 更改为 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 文件它有效(正如我所想的。我需要更多时间来测试它)。但这个解决方案严重制动程序。

也许你可以帮助我?

c# multithreading nhibernate sqlite locking
3个回答
5
投票

回答我自己。 问题与 .IsolationLevel(IsolationLevel.Serialized) 有关。当我将此行更改为 .IsolationLevel(IsolationLevel.ReadCommited) 时,问题消失了。


1
投票

sqlite 的设计就是像这样的“锁定”,因此名称中的 lite。它专为仅一个客户端连接而设计。

但是您可以为应用程序的不同区域使用多个数据库文件,这可能会拖延问题,直到您的用户群再次增长。


0
投票

我个人使用这个技巧:

假设我们有程序 A 输出 SQL 插入/更新或任何其他事务 程序B也做同样的事情。 (或 10-20 个程序/线程)

我这样做:

mkfifo mydbfifo
nohup sqlite3 mydb.db <mydbfifo &
nohup programA >mydbfifo &
nohup programB >mydbfifo &

等等..

© www.soinside.com 2019 - 2024. All rights reserved.