Конфликт блокировок 1С — ситуация, когда две операции одновременно хотят изменить одни и те же данные, и одна из них не дождалась своей очереди. Сотрудник видит сообщение «Конфликт блокировок при выполнении транзакции», документ не проводится, обмен обрывается на середине. Первый раздел написан для руководителя и обходится без кода, остальное — для программиста: поиск виновника и исправление через БлокировкаДанных.
Представьте склад, где остаток каждого товара записан в одной карточке. Пока один кладовщик вносит в неё изменения, второй ждёт. Блокировки в 1С работают так же: пока один документ меняет остаток товара, другой документ по этому товару стоит в очереди. Это нормальный механизм: он не даёт списать одну единицу товара дважды.
Проблема начинается, когда очередь не движется. У ожидания есть предел, по умолчанию 20 секунд. Если первая операция держит данные дольше, вторая получает ошибку. Данные при этом не портятся: 1С откатывает операцию целиком.
Запишите время, пользователя, операцию и полный текст сообщения — с этим специалист найдёт причину быстрее. Перезагрузка сервера «на всякий случай» снимает симптом, но не причину.
Начало сообщения одинаковое, а продолжение подсказывает, на каком уровне случился конфликт.
| Продолжение после «Конфликт блокировок при выполнении транзакции:» | Где возник | Что значит |
|---|---|---|
| Превышено максимальное время ожидания предоставления блокировки | Менеджер управляемых блокировок 1С | Транзакция ждала данные, занятые другим сеансом, дольше тайм-аута |
| Microsoft SQL Server …: Lock request time out period exceeded (native=1222) | MS SQL, чаще в автоматическом режиме | То же ожидание, но на уровне СУБД |
| … was deadlocked on lock resources with another process and has been chosen as the deadlock victim (native=1205) | MS SQL | Взаимоблокировка на уровне СУБД |
| ERROR: deadlock detected | PostgreSQL | Взаимоблокировка на стороне PostgreSQL |
Взаимоблокировка на управляемых блокировках начинается с той же фразы, а отличить её надёжнее по технологическому журналу: там будет событие TDEADLOCK, а не TTIMEOUT. Если в тексте есть код ошибки СУБД, конфликт возник в самой СУБД. Сообщение о том, что объект заблокирован другим пользователем, — другая история: это объектная блокировка документа, открытого на редактирование.
Режим задаётся свойством конфигурации «Режим управления блокировкой данных».
| Автоматический | Управляемый | |
|---|---|---|
| Кто блокирует | СУБД по своим правилам | Платформа через менеджер блокировок; СУБД блокирует минимально |
| Чтение запросом в транзакции | Блокирует прочитанные данные до конца транзакции | Ничего не блокирует без явной блокировки в коде |
| Что блокируется | MS SQL — строки и диапазоны, при эскалации вся таблица; PostgreSQL и файловая база — таблицы целиком | Конкретные значения полей, указанные в коде |
| Где встречается | Старые и многие самописные конфигурации | Современные типовые: БП 3.0, УТ 11, ERP и другие |
Из-за табличных блокировок на PostgreSQL управляемый режим практически обязателен. Файловой базе при активной многопользовательской работе поможет только переход на клиент-серверный вариант.
Ошибки блокировок 1С почти всегда сводятся к одному из сценариев ниже.
Искать нужно не того, кто получил ошибку, а того, кто держал данные: пострадавший сеанс обычно ни в чём не виноват.
Отбор по уровню «Ошибка» покажет пользователя, время и операцию пострадавшего. Затем посмотрите, что в те же секунды делали другие сеансы: длинная серия записей от одного пользователя или фонового задания — первый кандидат. Держателя блокировки журнал не показывает, но круг сужает.
Если проблема идёт прямо сейчас, откройте список сеансов базы и посмотрите захваченное соединение с СУБД и длительность текущего вызова: сеанс, который минутами держит соединение, — вероятный виновник. Колонки «Заблокировано упр.» и «Заблокировано СУБД» показывают номер соединения, которое держит нужные данные. Прерывать чужой сеанс стоит, только понимая, что это за операция: её транзакция откатится.
Самый точный инструмент, включается файлом logcfg.xml в каталоге conf платформы на сервере 1С. Для разбора блокировок нужны события TTIMEOUT (истекло ожидание управляемой блокировки), TDEADLOCK (взаимоблокировка) и TLOCK (установка управляемой блокировки).
В событиях TTIMEOUT и TLOCK есть свойство WaitConnections — номер соединения, которое держало данные. В TDEADLOCK участники взаимоблокировки перечислены в свойстве DeadlockConnectionIntersections: пары соединений и пространства блокировок, на которых они сцепились. По номеру соединения находите события TLOCK этого соединения, а в их контексте (Context) — строку кода, которая поставила блокировку.
<?xml version="1.0" encoding="UTF-8"?>
<config xmlns="http://v8.1c.ru/v8/tech-log">
<log location="D:\techlog\locks" history="4">
<event>
<eq property="Name" value="TTIMEOUT"/>
<eq property="p:processName" value="ИмяБазыВКластере"/>
</event>
<event>
<eq property="Name" value="TDEADLOCK"/>
<eq property="p:processName" value="ИмяБазыВКластере"/>
</event>
<event>
<eq property="Name" value="TLOCK"/>
<eq property="p:processName" value="ИмяБазыВКластере"/>
</event>
<property name="all"/>
</log>
</config>
Вместо ИмяБазыВКластере укажите имя своей базы в кластере, иначе журнал соберёт события всех баз. Событий TLOCK всё равно много: включайте журнал только на время воспроизведения проблемы, а потом удалите файл настройки.
При ошибках с SQL-кодом смотрите блокировки в самой СУБД: в MS SQL — sys.dm_exec_requests (колонка blocking_session_id), в PostgreSQL — pg_locks и pg_stat_activity.
Общий принцип для управляемого режима: блокируйте явно, узко и в начале транзакции — до чтения данных, по которым принимаете решение. Транзакцию держите короткой: всё, что можно, готовьте до неё. Все примеры ниже — для платформы 8.3 в управляемом режиме, СтрШаблон требует версии 8.3.6 или новее.
Типовой случай — документ списывает товар и проверяет остаток. В управляемом режиме запрос к остаткам ничего не блокирует: два документа одновременно прочитают «остаток 5», оба спишут по 5, и склад уйдёт в минус. Если же заблокировать весь регистр, документы будут проводиться строго по очереди. Правильно — заблокировать только нужные пары «склад + номенклатура».
// Модуль объекта документа «РасходТовара».
// Реквизит Склад, табличная часть Товары (Номенклатура, Количество).
// Регистр накопления «ОстаткиТоваров» (вид — остатки):
// измерения Склад и Номенклатура, ресурс Количество.
// Режим управления блокировкой — управляемый.
Процедура ОбработкаПроведения(Отказ, РежимПроведения)
// Транзакцию на время проведения открывает платформа, своя не нужна.
// 1. Одна строка на номенклатуру — без дублей.
ТаблицаТоваров = Товары.Выгрузить(, "Номенклатура, Количество");
ТаблицаТоваров.Свернуть("Номенклатура", "Количество");
// 2. Блокируем только нужные остатки, в исключительном режиме,
// и делаем это ДО чтения остатков.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиТоваров");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
ЭлементБлокировки.ИсточникДанных = ТаблицаТоваров;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
// 3. При перепроведении убираем старые движения документа,
// чтобы они не попали в остатки.
Движения.ОстаткиТоваров.Записывать = Истина;
Движения.ОстаткиТоваров.Записать();
// 4. Читаем остатки — они уже защищены нашей блокировкой.
// Проверяем остаток на момент документа; при оперативном проведении — текущий.
// Упрощение: документы, проведённые позже этого, здесь не перепроверяются.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Т.Номенклатура КАК Номенклатура,
| Т.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
| &ТаблицаТоваров КАК Т
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| ВТ_Товары.Номенклатура КАК Номенклатура,
| ВТ_Товары.Количество КАК Нужно,
| ЕСТЬNULL(Остатки.КоличествоОстаток, 0) КАК Есть
|ИЗ
| ВТ_Товары КАК ВТ_Товары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиТоваров.Остатки(
| &МоментКонтроля,
| Склад = &Склад
| И Номенклатура В
| (ВЫБРАТЬ
| ВТ.Номенклатура
| ИЗ
| ВТ_Товары КАК ВТ)) КАК Остатки
| ПО ВТ_Товары.Номенклатура = Остатки.Номенклатура
|ГДЕ
| ВТ_Товары.Количество > ЕСТЬNULL(Остатки.КоличествоОстаток, 0)";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Запрос.УстановитьПараметр("Склад", Склад);
Если РежимПроведения = РежимПроведенияДокумента.Оперативный Тогда
Запрос.УстановитьПараметр("МоментКонтроля", Неопределено); // текущие остатки
Иначе
// Задним числом: остаток до документа, без его собственных движений.
Запрос.УстановитьПараметр("МоментКонтроля", Новый Граница(МоментВремени(), ВидГраницы.Исключая));
КонецЕсли;
Выборка = Запрос.Выполнить().Выбрать();
// 5. Нехватка — отказ. Блокировка снимется вместе с откатом транзакции.
Пока Выборка.Следующий() Цикл
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = СтрШаблон("Не хватает ""%1"": требуется %2, на складе %3",
Выборка.Номенклатура, Выборка.Нужно, Выборка.Есть);
Сообщение.Сообщить();
Отказ = Истина;
КонецЦикла;
Если Отказ Тогда
Возврат;
КонецЕсли;
// 6. Формируем движения. Набор запишет платформа после обработчика,
// потому что Записывать = Истина.
Для Каждого СтрокаТовара Из ТаблицаТоваров Цикл
Движение = Движения.ОстаткиТоваров.ДобавитьРасход();
Движение.Период = Дата;
Движение.Склад = Склад;
Движение.Номенклатура = СтрокаТовара.Номенклатура;
Движение.Количество = СтрокаТовара.Количество;
КонецЦикла;
КонецПроцедуры
Разбор ключевых строк:
Режим = РежимБлокировкиДанных.Исключительный — остатки будут меняться. С разделяемым режимом второй документ прочитал бы те же остатки, а при записи обе транзакции упёрлись бы друг в друга — классическая взаимоблокировка.ИсточникДанных и ИспользоватьИзИсточникаДанных — значения номенклатуры берутся из свёрнутой таблицы, блокируется только то, что есть в документе.Блокировка.Заблокировать() — блокировка ставится здесь и держится до конца транзакции проведения. Метода, который снял бы её раньше, нет.Если транзакцию открываете вы сами, например для выдачи номера из регистра-счётчика, блокировка ставится первым действием, а обработчик исключения обязательно отменяет транзакцию.
// Общий модуль (сервер). Регистр сведений «СчетчикиНомеров», непериодический:
// измерение Префикс (Строка), ресурс Значение (Число).
Функция СледующийНомер(Префикс) Экспорт
// Явно просим управляемый режим: в конфигурации с режимом
// «Автоматический и управляемый» без параметра транзакция будет автоматической.
НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый);
Попытка
// Блокировка — первым действием внутри транзакции.
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.СчетчикиНомеров");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.УстановитьЗначение("Префикс", Префикс);
Блокировка.Заблокировать();
МенеджерЗаписи = РегистрыСведений.СчетчикиНомеров.СоздатьМенеджерЗаписи();
МенеджерЗаписи.Префикс = Префикс;
МенеджерЗаписи.Прочитать();
Номер = МенеджерЗаписи.Значение + 1;
МенеджерЗаписи.Префикс = Префикс; // на случай, если записи ещё не было
МенеджерЗаписи.Значение = Номер;
МенеджерЗаписи.Записать();
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Возврат Номер;
КонецФункции
Блокировка по префиксу не мешает сеансам с другими префиксами, а без неё два сеанса могут прочитать одно значение и выдать одинаковые номера.
Частая причина массовых конфликтов — обработка, которая открывает транзакцию до цикла по сотням документов: блокировки копятся до последнего документа.
// Выполняется на сервере (общий модуль или модуль объекта обработки).
// Плохо: блокировки всех документов и их движений держатся до конца цикла.
Процедура ПерепровестиОднойТранзакцией(СписокДокументов)
НачатьТранзакцию();
Попытка
Для Каждого ДокументСсылка Из СписокДокументов Цикл
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
// Лучше: каждый документ проводится в своей короткой транзакции.
Процедура ПерепровестиПоОдному(СписокДокументов)
Для Каждого ДокументСсылка Из СписокДокументов Цикл
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
Попытка
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
Исключение
ЗаписьЖурналаРегистрации("Перепроведение документов",
УровеньЖурналаРегистрации.Ошибка, ДокументСсылка.Метаданные(), ДокументСсылка,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
КонецПопытки;
КонецЦикла;
КонецПроцедуры
Если нужна атомарность («все документы или ни одного»), оставьте одну транзакцию, но уберите из неё всё лишнее и запускайте обработку вне рабочего времени. HTTP-запросы, работу с файлами и почтой из транзакции выносите всегда: до записи или в фоновое задание после неё.
Заблокировать() — в промежутке другой сеанс успевает изменить данные.Заблокировать() без неё приводит к ошибке выполнения.Добавить() без УстановитьЗначение и источника данных блокирует весь регистр, и документы по нему встают в очередь.ОтменитьТранзакцию() в обработчике исключения. Если внутри транзакции перехватить ошибку операции с базой (записи, блокировки, запроса) и продолжить работу, следующее обращение к базе завершится ошибкой «В данной транзакции уже происходили ошибки!». Правильный шаблон — в функции СледующийНомер выше: ОтменитьТранзакцию() и ВызватьИсключение в блоке Исключение.БлокировкаДанных появляются отрицательные остатки и двойные списания.Можно: параметр «Время ожидания блокировки данных» задаётся в конфигураторе в параметрах информационной базы. Но это лечит симптом: пользователи будут дольше ждать вместо быстрой ошибки. Годится как временная мера, пока ищете долгую транзакцию.
Нет, транзакция отменяется целиком, документ достаточно провести повторно. Исключение — обработки из нескольких транзакций: часть изменений уже записана, результат стоит проверить.
В конфигураторе откройте свойства корневого элемента конфигурации и найдите «Режим управления блокировкой данных». Если там «Автоматический и управляемый», режим задаётся у каждого документа и регистра свойством «Режим управления блокировкой». Явная транзакция НачатьТранзакцию() без параметра тогда будет автоматической; для управляемой укажите НачатьТранзакцию(РежимУправленияБлокировкойДанных.Управляемый).
ДокументОбъект.Заблокировать() — объектная блокировка: она не даёт другим сеансам редактировать и записывать этот объект, пока он открыт у вас, и действует вне транзакций. БлокировкаДанных работает только внутри транзакции и защищает данные, например остатки, от параллельного изменения до её завершения.
Если конфликты повторяются каждый день, а в базе много доработок и регламентных заданий, поиск виновника по технологическому журналу лучше отдать программисту, который делал это не раз. Посмотрите услугу «1С тормозит»: поиск долгих транзакций и блокировок или разместите задачу на площадке: оценка бесплатная, ответ — в течение рабочего дня.
Станьте частью сообщества!
Войдите или зарегистрируйтесь, и вы сможете участвовать в обсуждениях.