SQLite数据库被锁定多线程应用程序出错
本文关键字:应用 程序出错 多线程 锁定 数据库 SQLite | 更新日期: 2023-09-27 18:15:20
有一个多线程应用程序,可以处理大型DB文件(>600 Mb)。当我添加blob数据时,"数据库被锁定"问题开始出现,并且每个请求开始使用>30 Kb的blob数据。我认为问题与硬盘速度小有关。它看起来像SQLite删除-日志文件,我的应用程序的一个线程出锁(因为-日志文件被应用和删除),和其他我的线程想做史密斯与DB,但SQLite仍然更新DB文件…当然,我可以在每个DB调用后延迟一分钟,但这不是解决方案,因为我需要更快的速度。
现在我使用每个会话(每个线程)实现。因此,每个应用程序对象有一个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();
}
}
}
}
还有一个库助手类。正如您所看到的,每个DB调用都有锁,因此并发DB调用不能出现,对于不同的线程也是如此,因为_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()每个线程一次)。存储库由singletone实例化:
using (nHibernateHelper.OpenConnection())
{
Foo foo = new Foo();
FooRepository.Instance.Save(foo);
}
我试图将IsolationLevel更改为readcommitted,但这并没有改变问题。我还试图通过将SQLite日志模式从journal更改为WAL来解决这个问题:
using (nHibernateHelper.OpenConnection())
{
using (IDbCommand command = nHibernateHelper.CurrentSession.Connection.CreateCommand())
{
command.CommandText = "PRAGMA journal_mode=WAL";
command.ExecuteNonQuery();
}
}
这对有快速硬盘的电脑很有帮助,但在一些电脑上我也遇到了同样的错误。然后我尝试在存储库中添加"DB更新文件存在"检查,并在每次保存/更新/删除过程后延迟:
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);
}
}
相同的解决方案(检查DB更新文件)不适用于-journal文件。它报告,该-journal文件被删除,但我仍然得到错误。对于-wal文件,它工作(我认为)。我需要更多的时间来测试)。但这一解决方案严重阻碍了程序。
也许你能帮助我?自言自语。问题与。IsolationLevel(IsolationLevel.Serializable)有关。当我将这一行更改为。IsolationLevel(IsolationLevel.ReadCommitted)时,问题消失了。
sqlite被设计成这样的"锁定",因此名称中的life。它仅为一个客户端连接设计。
但是你可以为应用程序的不同区域使用多个数据库文件,这可能会拖延问题,直到你的用户基础再次增长。
我个人使用这个技巧:
假设我们有一个程序A输出SQL插入/更新或任何其他事务程序B也做同样的事情。(或10-20个程序/线程)
我这样做:
mkfifo mydbfifo
nohup sqlite3 mydb.db <mydbfifo &
nohup programA >mydbfifo &
nohup programB >mydbfifo &