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