NHibernate事务的不需要的行为

时间:2011-07-20 17:13:39

标签: asp.net nhibernate concurrency

我在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中的“动态更新”功能解决了这个问题 - 在我看来,这两个请求不会修改实体的同一个字段,所以它是可行的。

2 个答案:

答案 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();
}

您还应该检查您的交易级别。

要实现的好模式是乐观锁定(如果事务失败,请记录错误)。如果您的锁定有问题,您将在日志中看到错误,并能够调试代码。

http://knol.google.com/k/nhibernate-chapter-10-transactions-and-concurrency#10%282E%296%282E%29%28C2%29%28A0%29Pessimistic_Locking

答案 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();
}