Нежелательное поведение с транзакциями NHibernate - PullRequest
3 голосов
/ 20 июля 2011

У меня есть приложение ASP.NET MVC 3, использующее NHibernate над базой данных PostgreSQL.

На моем веб-сайте есть операция, которая принимает все сущности (я называю их единицами ) и, если они удовлетворяют определенному условию, обновляет одно из своих свойств (столбец).Вся эта операция заключена в транзакцию.

Сегодня я столкнулся с проблемой, когда заметил, что иногда (точно) одна единица не обновляется во время операции, даже если она должна (он удовлетворяет требуемому условию).

Теперь я обнаружил, что это потому, что в то же время на сервер поступает много других запросов, которые также имеют дело с единиц (для этого запроса это всегда один блок , который проверяется и, если необходимо, модифицируется).

Итак, в одном потоке у меня работает следующий код:

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 ]

2 голосов
/ 21 июля 2011

Я полагаю, ваша проблема здесь:

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

0 голосов
/ 21 июля 2011

+ 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();
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...