При интеграции 1С со сторонними базами данных на платформе MS SQL Server разработчики нередко сталкиваются с ошибкой: Transaction (Process ID ...) was deadlocked on lock | communication buffer resources with another process and has been chosen as the deadlock victim. Rerun the transaction. Разберем подробно, почему возникает эта ситуация при параллельной работе нескольких информационных баз или регламентных заданий и какими способами можно устранить проблему.
Проанализируем техническую природу ошибки. Взаимоблокировка (Deadlock) возникает тогда, когда два или более параллельных процесса захватывают ресурсы в разном порядке и бесконечно ожидают освобождения ресурсов друг другом. В сообщении об ошибке также часто фигурирует communication buffer resources, что указывает на специфические условия:
INSERT) или обновления (UPDATE) СУБД автоматически повышает блокировки отдельных строк до блокировок страниц или всей таблицы целиком.Table Scan), СУБД блокирует значительно больший диапазон данных, чем требуется для конкретной операции.Прежде чем менять код, необходимо точно определить участников конфликта и заблокированные ресурсы. Для этого проанализируем граф взаимоблокировки в MS SQL Server:
system_health регистрируются все случаи взаимоблокировок. Откроем отчет xml_deadlock_report в среде SQL Server Management Studio (SSMS), чтобы увидеть точные тексты конфликтующих запросов и типы захваченных блокировок.
DBCC TRACEON (1222, -1);
Это позволит получать подробный текстовый отчет о структуре взаимных блокировок в журнале SQL Server.Поскольку СУБД сама рекомендует повторить транзакцию (Rerun the transaction), реализуем в коде 1С обработку исключения с механизмом повторных попыток (Retry Logic). Также важно разбить большие объемы данных на небольшие порции, чтобы избежать эскалации блокировок.
Рассмотрим пример организации повторного выполнения вызова процедуры через ADO с паузой:
МаксимальноеКоличествоПопыток = 5;
ПопыткаВыполнения = 1;
Успешно = Ложь;
Пока ПопыткаВыполнения <= МаксимальноеКоличествоПопыток И Не Успешно Цикл
Попытка
КомандаSQL.Execute();
Успешно = Истина;
Исключение
ТекстОшибки = ОписаниеОшибки();
Если СтрНайти(ТекстОшибки, "deadlock") > 0 Тогда
Если ПопыткаВыполнения = МаксимальноеКоличествоПопыток Тогда
ВызватьИсключение "Превышено количество попыток устранения дедлока: " + ТекстОшибки;
КонецЕсли;
// Делаем небольшую случайную задержку перед повтором
Приостановить(1 + (ПопыткаВыполнения * 2));
ПопыткаВыполнения = ПопыткаВыполнения + 1;
Иначе
ВызватьИсключение ТекстОшибки;
КонецЕсли;
КонецПопытки;
КонецЦикла;
Если несколько баз 1С одновременно вызывают одну и ту же процедуру постобработки, разберем способ упорядочивания очередей на уровне MS SQL Server с помощью механизма sp_getapplock. Это предотвращает параллельный запуск тяжелого участка кода без необходимости блокировать таблицы данных.
Посмотрим на пример оборачивания логики внутри хранимой процедуры:
CREATE OR ALTER PROCEDURE dbo.AgreeNumLike_upd
AS
BEGIN
SET NOCOUNT ON;
DECLARE @LockResult int;
-- Запрашиваем эксклюзивную прикладную блокировку
EXEC @LockResult = sp_getapplock
@Resource = 'AgreeNumLike_upd_Lock',
@LockMode = 'Exclusive',
@LockOwner = 'Session',
@LockTimeout = 60000; -- Ожидание до 60 секунд
IF @LockResult >= 0
BEGIN
BEGIN TRY
-- Основной код обновления данных
UPDATE dbo.Contracts
SET SearchField = dbo.FormatNumber(ContractNumber)
WHERE NeedUpdate = 1;
END TRY
BEGIN CATCH
-- Освобождаем блокировку при возникновении ошибки
EXEC sp_releaseapplock
@Resource = 'AgreeNumLike_upd_Lock',
@LockOwner = 'Session';
THROW;
END CATCH;
-- Освобождаем блокировку после успешного выполнения
EXEC sp_releaseapplock
@Resource = 'AgreeNumLike_upd_Lock',
@LockOwner = 'Session';
END
ELSE
BEGIN
RAISERROR('Не удалось захватить блокировку для выполнения процедуры.', 16, 1);
END
END;
Рассмотрим дополнительные методы на уровне конфигурации базы данных MS SQL Server:
communication buffer resources), добавим в конец проблемного SQL-запроса подсказку OPTION (MAXDOP 1). Это принудительно заставит запрос выполняться в один поток.READ_COMMITTED_SNAPSHOT, чтобы операции чтения не блокировали операции записи:
ALTER DATABASE [ИмяБазыСайта] SET READ_COMMITTED_SNAPSHOT ON WITH ROLLBACK IMMEDIATE;