Parallel.ForEach используется с NHibernate, что приводит к блокировкам SQL Server
Во-первых, я не очень разбираюсь в многопоточности и параллельном программировании.
Я пытаюсь оптимизировать производительность устаревшего приложения (.Net 4
, NHibernate 2.1
).
** Пока что модернизация NHibernate
не является приоритетом, но находится в стадии разработки.
Со временем производительность стала кошмаром с ростом данных. Одна вещь, которую я видел, это Parallel.ForEach
оператор, который вызывает метод, который выбирает и обновляет сложную сущность (с несколькими взаимосвязями - свойства и коллекции).
Кусок кода имеет следующую форму (упрощено для ясности):
void SomeMethod(ICollection<TheClass> itemsToProcess)
{
Parallel.ForEach(itemsToProcess, item => ProcessItem(item);
}
TheClass ProcessItem(TheClass i)
{
var temp = NHibernateRepository.SomeFetchMethod(i);
var result = NHibernateRepository.Update(temp);
return result;
}
SQL Server периодически сообщает об ошибках блокировки базы данных со следующей ошибкой:
Transaction (Process ID 20) was deadlocked on lock resources with another process and has been chosen as the deadlock victim. Rerun the transaction
Я подозреваю, что это из-за какого-то состояния гонки, приводящего к тупику, даже если ISessions
являются отдельными.
ICollection<TheClass>
может иметь до 1000 элементов, каждый со свойствами и вложенными коллекциями, которые обрабатываются, генерируя много SELECT
а также UPDATE
заявления (подтверждено с помощью "NHibernate Profiler")
Есть ли лучший способ справиться с этим параллельно, или я должен рефакторинг кода в традиционный цикл?
Я знаю, что могу альтернативно реализовать свой код, используя:
foreach
петля в том жеISession
контекст- Сессией без гражданства
- С
Environment.BatchSize
установить разумное значение
ИЛИ ЖЕ
- Использование SQL BulkCopy
Я также прочитал довольно много полезной информации о взаимоблокировках SQL Server и Parallel.ForEach
Быть легкой ловушкой:
1 ответ
Это очень сложная тема. Есть одна стратегия, которая гарантированно безопасна и, вероятно, приведет к ускорению:
Повторите попытку в случае тупика.
Поскольку взаимоблокировка откатывает транзакцию, вы можете безопасно повторить всю транзакцию. Если частота взаимоблокировок низкая, ускорение параллелизма будет высоким.
Хорошая вещь о повторных попытках состоит в том, что вы можете сделать простое изменение кода в центральном месте.
Поскольку это не видно из опубликованного кода: убедитесь, что потоки не разделяют сеанс или сущности. Ни один из них не является потокобезопасным.