我在PostgreSQL数据库上使用NHibernate的ASP.NET MVC 3应用程序。
我的网站上有一个操作,它接受所有实体(我称之为单位),如果它们满足某些条件,它会更新其中一个属性(列)。整个操作都包含在交易中。
今天我遇到了一个问题,我注意到有时,(确切地说)一个单位在操作期间没有得到更新,即使它应该(它满足所需条件)。
现在我发现这是因为同时还有很多其他请求进入服务器,这些请求也处理单位(对于此请求,它总是一个单位经过检查,如果需要,可以进行修改。
所以在一个线程中我运行了这个代码:
ISession session = sessionBuilder.GetSession();
using (ITransaction transaction = session.BeginTransaction())
{
var unitsToBeUpdated = session.QueryOver<Unit>()
.Where(x => x.Status == STATUS_TRANSLATED)
.JoinQueryOver<UnitParent>(u => u.Parent)
.JoinQueryOver<Document>(p => p.Document)
.Where(d => d.Job == job);
foreach (Unit unit in unitsToBeUpdated.List<Unit>())
{
unit.Status = STATUS_CONFIRMED;
session.SaveOrUpdate(unit);
}
transaction.Commit();
}
并且在第二个线程中我运行此代码(多次,每次运行不同的unitId
)
Unit unit;
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
unit = unitRepository.GetById(unitId);
}
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
unit.LastCategory = statisticsHarvester.AddWords(job, unit);
unitRepository.Update(unit);
transaction.Commit();
}
在第二个线程中,事务开放显示两次,因为事实上这两个事务是在不同的函数中创建的。
所以请求就像
Request A started...
Request B(unitId = 0) started...
Request B(unitId = 0) finished
Request B(unitId = 1) started...
Request B(unitId = 1) finished
....
Request B(unitId = n) started...
Request A finished
Request B(unitId = n) finished
Request B(unitId = n+1) started...
Request B(unitId = n+1) finished
....
也许我没有完全了解事务和并发访问的整个世界,但在这种情况下,第一个线程是否不可能只更新某些单元?我预计整个交易会回滚,出现问题。 这有什么问题?
谢谢。
编辑:最后我用NHibernate中的“动态更新”功能解决了这个问题 - 在我看来,这两个请求不会修改实体的同一个字段,所以它是可行的。
答案 0 :(得分:2)
我想你的问题就在这里:
Unit unit;
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
unit = unitRepository.GetById(unitId);
}
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
unit.LastCategory = statisticsHarvester.AddWords(job, unit);
unitRepository.Update(unit);
transaction.Commit();
}
在这里,您执行阅读,然后开始新的事务。在两个事务之间,可以在数据库中更改单元。在第二个事务中,您将旧单元保存回数据库。最后写在这里获胜。
重写代码如:
Unit unit;
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
unit = unitRepository.GetById(unitId); //Add lock mode in you getbyid function LockMode.Upgrade
unit.LastCategory = statisticsHarvester.AddWords(job, unit);
unitRepository.Update(unit);
transaction.Commit();
}
您还应该检查您的交易级别。
要实现的好模式是乐观锁定(如果事务失败,请记录错误)。如果您的锁定有问题,您将在日志中看到错误,并能够调试代码。
答案 1 :(得分:0)
+1对等人的回答。为了最大限度地减少这类问题,请考虑一个事务应该包含一个操作。
所以在你的“第二个线程”中尝试在那个事务中尽可能地组装,因为操作是更新给定单元的LastCategory属性:
using (ITransaction transaction = sessionBuilder.GetSession().BeginTransaction())
{
foreach (Unit unit in unitRepository.GetByIds(unitIds) { //an array of unitIds or something similar
unit.LastCategory = statisticsHarvester.AddWords(job, unit);
unitRepository.Update(unit);
}
transaction.Commit();
}