Ошибки «превышено время ожидания блокировки» и продажа одного товара дважды обычно происходят из-за непонимания блокировок. Это любимая тема собесов middle и senior.
Транзакции
Транзакция выполняется целиком или не выполняется. Неявно транзакция открывается при записи объекта: внутри неё выполняются ПередЗаписью, ПриЗаписи, ОбработкаПроведения и запись наборов. Явно:
НачатьТранзакцию();
Попытка
// блокировки, чтение, запись
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Стандарт 1С: начинать до Попытка, в Исключение сначала отменять, потом логировать и пробрасывать.
Вложенных транзакций нет. Платформа считает уровень вложенности, но транзакция одна. ОтменитьТранзакцию на любом уровне отменяет всё, и дальнейшие операции с базой падают с ошибкой «в данной транзакции уже происходили ошибки».
Транзакции должны быть короткими: никаких вопросов пользователю, HTTP-запросов и тяжёлых вычислений внутри.
Режимы блокировок
- Автоматический: блокировки СУБД, устаревший.
- Управляемый: блокировки на сервере 1С по данным. Используется в современных конфигурациях.
- Автоматический и управляемый: переходный.
Управляемые блокировки
Ставятся менеджером блокировок сервера 1С на пространство (например, регистр) и значения полей. Живут до конца транзакции.
- Исключительная: несовместима ни с какой другой на те же данные.
- Разделяемая: совместима с разделяемыми, несовместима с исключительной.
Платформа сама ставит исключительную блокировку на записываемые данные. Чтение запросом не блокирует. Поэтому в схеме «прочитать, проверить, записать» разработчик ставит блокировку явно, до чтения:
Блокировка = Новый БлокировкаДанных;
Элемент = Блокировка.Добавить("РегистрНакопления.ТоварыНаСкладах");
Элемент.Режим = РежимБлокировкиДанных.Исключительный;
Элемент.УстановитьЗначение("Склад", Склад);
Элемент.ИсточникДанных = Товары;
Элемент.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
Блокировка.Заблокировать();
Заблокировать() вне транзакции вызывает ошибку. Предложение запроса ДЛЯ ИЗМЕНЕНИЯ в управляемом режиме игнорируется.
Ожидание и взаимоблокировки
Если данные заняты, транзакция ждёт. После таймаута (по умолчанию около 20 секунд) пользователь получает «Конфликт блокировок... превышено максимальное время ожидания». Виновник почти всегда длинная транзакция в другом сеансе.
Взаимоблокировка: две транзакции держат разделяемые блокировки и хотят исключительные на те же данные, ждут друг друга, и платформа отменяет одну. Профилактика:
- сразу ставить исключительную блокировку на то, что будете менять;
- брать блокировки в одном порядке;
- блокировать только нужные значения, а не всю таблицу.
Диагностика
Технологический журнал: события TLOCK (установка), TTIMEOUT (таймаут ожидания), TDEADLOCK (взаимоблокировка) с контекстом модуля и строки. Для мониторинга ЦУП из КИП.
Частые ошибки
- Чтение остатков без блокировки перед списанием.
- НачатьТранзакцию внутри Попытка, Исключение без ОтменитьТранзакцию.
- Вопрос пользователю или HTTP-запрос в транзакции.
- Обмен данными одной огромной транзакцией в рабочее время.
Что спросят на собесе
- Чем исключительная блокировка отличается от разделяемой?
- Почему в управляемом режиме нужно блокировать перед чтением?
- Что будет при ОтменитьТранзакцию во вложенной процедуре?
- Как найти, кто держит блокировку?
- Как избежать взаимоблокировок?