我有一个多线程应用程序,适用于大型数据库(文件大小> 1 Gb,数据库有38个表,每个表超过500 K实体)。它使用 Castle 3.1.0.0 , NHibernate 3.3.1.4000 , FluentNibernate 1.3.0.733 , SQL Server 2012 。
以下一种方式配置NHibernate:
config.SetProperty(Environment.CommandTimeout, "300");
config.SetProperty(Environment.BatchSize, "0");
config.SetProperty(Environment.GenerateStatistics, "true");
config.SetProperty(Environment.ReleaseConnections, "auto");
config.SetProperty(Environment.UseQueryCache, "true");
config.SetProperty(Environment.SqlExceptionConverter, typeof(MsSqlExceptionConverter).AssemblyQualifiedName);
//...
.MaxFetchDepth(1)
我使用每个线程一个会话(Castle.Windsor)和短交易。每个数据库更新,保存,删除程序由代码锁定:
public abstract class BaseEntityRepository<T, TId> : IBaseEntityRepository<T, TId> where T : BaseEntity<TId> {
protected static readonly object Locker = new object();
public bool Save(T item) {
bool result = false;
if ((item != null) && (item.IsTransient())) {
lock (Locker) {
using (ITransaction tr = Session.BeginTransaction()) {
try {
Session.Save(item);
if ((tr.IsActive) && (!tr.WasCommitted) && (!tr.WasRolledBack))
tr.Commit();
result = true;
} catch {
if ((tr.IsActive) && (!tr.WasCommitted) && (!tr.WasRolledBack))
tr.Rollback();
Session.Clear();
throw;
}
}
}
}
return result;
}
//same for delete and update
public T Get(TId itemId) {
T result = default(T);
try {
result = Session.Get<T>(itemId);
} catch {
throw;
}
return result;
}
public IList<T> Find(Expression<Func<T, bool>> predicate) {
IList<T> result = new List<T>();
try {
result = Session.Query<T>().Where(predicate).ToList();
} catch {
throw;
}
return result;
}
}
在将3个数据库(每个300-400 Mb)合并到一个大数据库(上面描述)之前,一切正常。我通过Microsoft SQL Server Management Studio导出/导入数据向导合并数据库。合并后,我设置了主键,并为 Id 列设置了标识规范。在此之后我得到了许多 SQL Server 错误(这些错误没有被复制,它们似乎不时出现):
事务(进程ID X)在锁资源上与另一个进程发生死锁,并被选为死锁牺牲品。重新运行该交易。
所以我看了this article,this。我做了所有步骤,发现两个线程在同一个associatedObjectId上做同一个页面锁定。我甚至不明白它是怎么回事,因为我已经以编程方式锁定每个实体更新,所以它甚至不能成为这样的问题。这就是我将数据库更改为快照隔离的原因:
ALTER DATABASE MY_DB 设置ALLOW_SNAPSHOT_ISOLATION
ALTER DATABASE MY_DB SET READ_COMMITTED_SNAPSHOT ON
我还将NHibernate IsolationLevel 从 ReadCommitted 更改为快照来解决此问题。在此之后我得到了这个错误:
无法执行命令:UPDATE [ Foo ] SET Field1 = @ p0,Field2 = @ p1 WHERE Id = @ p2
System.Data.SqlClient.SqlException(0x80131904):由于更新冲突而导致快照隔离事务中止。您不能使用快照隔离直接或间接访问数据库'My_DB'中的表'dbo。 Bar '来更新,删除或插入已被其他事务修改或删除的行。重试事务或更改更新/删除语句的隔离级别。
in System.Data.SqlClient.SqlConnection.OnError(SqlException exception, Boolean breakConnection)
in System.Data.SqlClient.TdsParser.ThrowExceptionAndWarning()
in System.Data.SqlClient.TdsParser.Run(RunBehavior runBehavior, SqlCommand cmdHandler, SqlDataReader dataStream, BulkCopySimpleResultSet bulkCopyHandler, TdsParserStateObject stateObj)
in System.Data.SqlClient.SqlCommand.FinishExecuteReader(SqlDataReader ds, RunBehavior runBehavior, String resetOptionsString)
in System.Data.SqlClient.SqlCommand.RunExecuteReaderTds(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, Boolean async)
in System.Data.SqlClient.SqlCommand.RunExecuteReader(CommandBehavior cmdBehavior, RunBehavior runBehavior, Boolean returnStream, String method, DbAsyncResult result)
in System.Data.SqlClient.SqlCommand.InternalExecuteNonQuery(DbAsyncResult result, String methodName, Boolean sendToPipe)
in System.Data.SqlClient.SqlCommand.ExecuteNonQuery()
in NHibernate.AdoNet.AbstractBatcher.ExecuteNonQuery(IDbCommand cmd)
无法将数据库状态与会话同步 NHibernate.StaleObjectStateException:行已被另一个事务更新或删除(或未保存的值映射不正确):[ Foo#544353 ]
in NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)
in NHibernate.Persister.Entity.AbstractEntityPersister.UpdateOrInsert(Object id, Object[] fields, Object[] oldFields, Object rowId, Boolean[] includeProperty, Int32 j, Object oldVersion, Object obj, SqlCommandInfo sql, ISessionImplementor session)
in NHibernate.Persister.Entity.AbstractEntityPersister.Update(Object id, Object[] fields, Int32[] dirtyFields, Boolean hasDirtyCollection, Object[] oldFields, Object oldVersion, Object obj, Object rowId, ISessionImplementor session)
in NHibernate.Action.EntityUpdateAction.Execute()
in NHibernate.Engine.ActionQueue.Execute(IExecutable executable)
in NHibernate.Engine.ActionQueue.ExecuteActions(IList list)
in NHibernate.Engine.ActionQueue.ExecuteActions()
in NHibernate.Event.Default.AbstractFlushingEventListener.PerformExecutions(IEventSource session)
NHibernate.TransactionException:事务未连接或已断开连接
in NHibernate.Transaction.AdoTransaction.CheckNotZombied()
in NHibernate.Transaction.AdoTransaction.Rollback()
in My.DBLayer.Data.Repositories.BaseEntityRepository`2.Update(T item)
我没有数据库实体的版本属性,并且我的代码中没有OptimisticLock.Version()行,所以我使用了PessimisticLock implicity。我可以添加版本和 OptimisticLock ,但不要认为这会解决问题。
我试着做简单的测试:
Thread t1 = new Thread(m1);
Thread t2 = new Thread(m2);
t1.Start();
t2.Start();
private static void m1() {
FooRepository rep1 = BootstrapContainer.Instance.Resolve<FooRepository>();
Foo foo1 = rep1.Get(1);
foo1.Field1 = "bbbb";
Thread.Sleep(60*1000);
rep1.Update(foo1);
}
private static void m2() {
FooRepository rep2 = BootstrapContainer.Instance.Resolve<FooRepository>();
Thread.Sleep(5*1000);
Foo foo2 = rep2.Get(1);
foo2.Field2 = "aaaaa";
Thread.Sleep(5*1000);
rep2.Update(foo2);
}
一切正常,没有任何错误。
为什么我有这些错误(我没有更改代码,只是将数据库合并为一个,并且在合并之前一切正常)?如果我使用锁定来同时阻止不同线程中的更新实体,为什么会出现这种错误。
答案 0 :(得分:0)
您可以从查询并行化中获得死锁...也就是说,单个查询在将工作拆分为多个并行操作时可能会自动死锁。我曾经多次遇到这种情况。如果您使用的是hql,则可以在查询/语句中添加OPTION(MAXDOP 1)以查看是否可以解决问题。
或者,您可以将整个服务器设置为MAXDOP 1 ...这意味着您永远不会获得并行性(https://msdn.microsoft.com/en-us/library/ms189094.aspx)。如果不深入分析工作负载,我不建议这样做,但它可以帮助您查看并行性是否是死锁的来源。