В типовых решениях «1С:Предприятие», таких как «1С:Розница», процедура закрытия кассовой смены является одной из самых ресурсоемких операций. Часто системные администраторы и программисты сталкиваются со странной ситуацией: несмотря на то, что программный код устанавливает управляемую блокировку только на конкретные документы (чеки ККМ) определенной кассы, пользователи на других кассах не могут пробить чек из-за ошибки «Конфликт блокировок при выполнении транзакции». Рассмотрим подробнее, почему зоны действия блокировок, которые теоретически не должны пересекаться, вызывают взаимные ожидания, и как с этим бороться.
Проанализируем фрагмент кода, который чаще всего становится инициатором проблемы при закрытии смены. Программа пытается заблокировать все проведенные чеки, которые еще не были архивированы, по конкретной кассе:
УстановитьПривилегированныйРежим(Истина);
ЗапросПоЧекам.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.ЧекККМ КАК Док
|ГДЕ
| Док.КассаККМ = &КассаККМ
| И Док.Проведен
| И НЕ Док.СтатусЧекаККМ = ЗНАЧЕНИЕ(Перечисление.СтатусыЧековККМ.Архивный)";
РезультатЗапросаПоЧекам = ЗапросПоЧекам.Выполнить();
НачатьТранзакцию();
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить("Документ.ЧекККМ");
ЭлементБлокировки.ИсточникДанных = РезультатЗапросаПоЧекам.Выгрузить();
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Ссылка", "Ссылка");
Блокировка.Заблокировать();
На первый взгляд, использование объекта БлокировкаДанных с указанием конкретных ссылок через ИспользоватьИзИсточникаДанных должно гарантировать, что будут заблокированы только те записи, которые относятся к закрываемой кассе. Однако на практике запись нового чека на совершенно другой кассе «встает в очередь» и отваливается по таймауту (обычно через 20 секунд). Выясним основные причины этого поведения.
Важно понимать, что управляемые блокировки живут не в СУБД (MS SQL, PostgreSQL), а в памяти сервера приложений «1С:Предприятие». У менеджера управляемых блокировок есть свои алгоритмы оптимизации. Если количество объектов, передаваемых в ИсточникДанных, слишком велико (по разным оценкам, порог составляет от 20 000 до 100 000 записей), сервер 1С может принять решение о трансформации блокировки. Вместо того чтобы хранить в памяти тысячи отдельных ссылок, он блокирует всё пространство имен целиком — в данном случае весь тип Документ.ЧекККМ.
Как только произошла такая эскалация, любая попытка записи любого чека в базе будет ожидать завершения транзакции закрытия смены. Чтобы проверить эту гипотезу, проанализируем количество документов, попадающих в выборку. Если их счет идет на тысячи, вероятность эскалации крайне высока.
Даже если мы явно блокируем только документы, процесс закрытия смены в «1С:Розница» неизбежно сопровождается записью движений в регистры накопления и сведений. Проанализируем, какие ресурсы могут быть общими для разных касс:
Проанализируем ситуацию: если новый чек пытается сделать запись в те же регистры, по которым сейчас «массированно» пишет транзакция закрытия смены, возникнет ожидание на уровне СУБД или менеджера управляемых блокировок 1С.
Разберем ситуацию, когда ошибка выглядит как TTIMEOUT, но причина кроется в аппаратной части. Когда выполняется транзакция закрытия смены, сервер СУБД испытывает колоссальную нагрузку на дисковую подсистему и процессор. В этот момент:
КассаККМ в таблице чеков не имеет эффективного индекса, SQL-запрос для выборки чеков может вызвать полное сканирование таблицы (Table Scan), накладывая разделяемые блокировки (S-locks) на огромные диапазоны данных.Чтобы не гадать, а точно знать причину, нам необходимо настроить сбор данных Технологического Журнала (ТЖ). Нас интересуют два события: TTIMEOUT (само событие ошибки) и TLOCK (установка блокировок). Рассмотрим пример настройки logcfg.xml:
<config xmlns="http://v8.1c.ru/7.4/config/logcfg">
<log location="C:\Logs\Locks" history="24">
<event>
<eq property="Name" value="TTIMEOUT"/>
</event>
<event>
<eq property="Name" value="TLOCK"/>
</event>
<property name="all"/>
</log>
</config>
При анализе логов обратим внимание на следующие параметры:
Выяснив причину, мы можем применить один из следующих подходов для минимизации конфликтов:
Вместо того чтобы блокировать и обрабатывать все чеки за смену одной транзакцией, попробуем реализовать порционную обработку. Разберем логику: обрабатываем по 100-200 чеков, фиксируем транзакцию и делаем небольшую паузу. Это позволит другим кассам «проскочить» со своими короткими транзакциями записи чека в промежутках между нашими блоками.
Убедимся, что все поля, участвующие в отборах (особенно КассаККМ и СтатусЧекаККМ), проиндексированы. Также проанализируем фрагментацию индексов и актуальность статистики в MS SQL. Высокая фрагментация часто приводит к тому, что СУБД выбирает неоптимальный план запроса с избыточными блокировками.
Если конфликт происходит на уровне регистров накопления, проверим, включен ли режим Разделение итогов для этих регистров. Этот механизм позволяет разным сеансам одновременно писать данные в регистр, создавая отдельные записи в таблице итогов, которые позже объединяются фоновым заданием.
Подводя итог, можно сказать, что конфликт блокировок между разными кассами — это чаще всего признак либо избыточной эскалации на сервере 1С из-за большого объема данных, либо «проседания» производительности СУБД, заставляющей транзакции длиться дольше допустимого порога. Тщательный анализ технологического журнала позволит вам увидеть реальную картину и устранить узкое место.