1C-CodeОсновы 1С → Что тормозит и почему

Что тормозит и почему

Девять причин, по которым код работает минуты вместо секунд. Почти все видны глазами, если знать, куда смотреть.

Глава 28 из 36 · чтение примерно 26 минут

«Обработка идёт сорок минут» почти никогда не значит, что данных много. Обычно это одна из пяти ошибок, и все они опознаются по виду кода, без всяких замеров.

1. Запрос в цикле

Самая частая и самая дорогая. Цикл по тысяче строк, внутри — запрос или поиск. Тысяча походов в базу вместо одного.

Опознаётся мгновенно: Новый Запрос или НайтиПо… внутри Для Каждого.

Лечится всегда одинаково: вынести запрос из цикла и передать список параметром. Один запрос по тысяче элементов вместо тысячи запросов по одному.

2. Отбор в ГДЕ вместо параметров

Про это была целая глава. Виртуальная таблица без параметров собирается целиком, а потом лишнее выбрасывается. Опознаётся так: .Остатки() с пустыми скобками и условие в ГДЕ ниже.

3. Соединение с подзапросом

Соединять таблицу с вложенным запросом — значит просить СУБД выполнить его и не дать ей возможности использовать индексы по-человечески.

Лечится временными таблицами: положить промежуточный результат в ПОМЕСТИТЬ, проиндексировать ИНДЕКСИРОВАТЬ ПО и соединять уже с ней.

4. Условие, под которое нет индекса

Отбор по полю, которое не входит ни в один индекс, заставляет читать таблицу целиком.

В 1С индексы появляются не сами: измерения регистров индексируются, реквизиты справочников — только если поставить свойство «Индексировать». Отбор по неиндексированному реквизиту на справочнике в сто тысяч элементов — это сто тысяч прочитанных строк.

5. Обращение к реквизиту через точку в цикле

Строка.Номенклатура.Артикул внутри цикла — это отдельный поход в базу на каждой итерации. Ссылка знает только идентификатор; за артикулом платформа идёт в справочник.

В запросе такая точка безобидна — она превращается в соединение. В коде она стоит дорого.

Лечится тем же: получить нужные поля запросом сразу, а не добирать их по одному.

6. Условие, по которому нельзя искать быстро

Индекс — это оглавление: по нему база находит нужные строки, не читая таблицу целиком. Есть условия, при которых оглавление бесполезно, и их узнают по виду.

ПОДОБНО "%текст%" — поиск подстроки где угодно внутри. Оглавление отсортировано по началу строки, а начало здесь неизвестно: приходится читать всё. А вот ПОДОБНО "текст%" — по началу — индексом пользуется.

Функция вокруг поля. ГДЕ ГОД(Дата) = 2026 — база вынуждена посчитать год у каждой строки. То же условие как Дата МЕЖДУ &Начало И &Конец ложится на индекс.

НЕ и ИЛИ по разным полям. «Всё, кроме» — это почти вся таблица, и читать её целиком дешевле, чем ходить по оглавлению.

Что индексируется в 1С само

ЧтоИндекс естьЧто из этого следует
Ссылка у любого объектавсегдапоиск и соединение по ссылке — дёшево
Код и наименование справочникавсегдаНайтиПоКоду и НайтиПоНаименованию быстрые
Измерения регистравсегда, в порядке объявленияотбор по ПЕРВЫМ измерениям работает, по последним — хуже
Реквизит справочника или документатолько если поставить «Индексировать»иначе отбор по нему читает таблицу целиком
Реквизит регистранетреквизит не для отбора — для отбора заводят измерение
Поле временной таблицытолько если написать ИНДЕКСИРОВАТЬ ПОбез этого соединение с ней перебирает всё

Порядок измерений регистра — это не оформление

Индекс по измерениям строится в том порядке, в каком они объявлены. Отбор по первому измерению использует его целиком, отбор по одному последнему — почти нет.

Поэтому измерения ставят по убыванию «как часто по нему отбирают»: обычно первым идёт то, что режет данные сильнее — организация, склад, номенклатура.

Поменять порядок потом можно, и это дешёвая правка в конфигураторе, — но заметить проблему без замера нельзя: отчёт просто «немного медленный».

7. Всё одним запросом там, где нужно два

Сложный запрос с тремя соединениями, вложенным подзапросом и группировкой база оптимизирует хуже, чем два простых. Особенно когда внутри есть виртуальные таблицы: каждая — сама по себе целый запрос.

Инструмент для этого — пакетный запрос с временными таблицами. Сначала положить во временную таблицу то, что уже отобрано и свёрнуто, проиндексировать её — и только потом соединять.

Выигрыш здесь не в красоте: во временной таблице лежит мало строк, и база точно знает, сколько именно, — а про подзапрос она этого не знает и выбирает план наугад.

Пакет с временной таблицей

ВЫБРАТЬ
	Товар,
	СУММА(Количество) КАК Количество
ПОМЕСТИТЬ ВТ_Строки
ИЗ
	Документ.РеализацияТоваров.Товары
СГРУППИРОВАТЬ ПО
	Товар
ИНДЕКСИРОВАТЬ ПО
	Товар
;
ВЫБРАТЬ … ИЗ ВТ_Строки КАК С ЛЕВОЕ СОЕДИНЕНИЕ …
ЧастьЧто означает
ПОМЕСТИТЬ ВТ_Строкирезультат ложится во временную таблицу, а не отдаётся наружу
СГРУППИРОВАТЬ ПОсворачиваем ДО соединения — именно это и убирает размножение строк
ИНДЕКСИРОВАТЬ ПОбез этой строки соединение с временной таблицей перебирает её целиком
точка с запятойразделитель запросов пакета; выполняются они по очереди, в одной транзакции

Индексировать по тому, по чему соединяют

Правило для ИНДЕКСИРОВАТЬ ПО простое: перечисляют поля, по которым эту временную таблицу будут соединять или отбирать дальше.

Индексировать всё подряд смысла нет: построение индекса само по себе работа, и на таблице в десять строк она дороже перебора.

Здесь тот же принцип, что и везде в этой главе: на трёх строках разницы не видно, а решение принимается именно на трёх строках.

8. Ждёт, а не считает

«Долго» бывает двух разных видов, и лечатся они по-разному.

Много работы — база честно читает миллион строк. Видно в замере: время распределено по запросу.

Ожидание на блокировке — база не делает ничего, она ждёт, пока чужая транзакция отпустит данные. В замере это выглядит как «один запрос выполнялся сорок секунд», хотя сам по себе он мгновенный.

Отличить просто: то, что ждёт, ведёт себя по-разному в разное время дня. Отчёт, который утром считается за секунду, а в конце месяца — за минуту, почти наверняка стоит в очереди, а не считает.

Долгая транзакция мешает всем

Обратная сторона: ваша обработка, открывшая транзакцию на десять минут, держит блокировки все десять минут. Остальные в это время «тормозят», и виноватым будет выглядеть их код.

Отсюда два правила. Не держать транзакцию дольше необходимого — обрабатывать порциями, фиксируя каждую. Не обращаться к внешним системам внутри транзакции — чужой сервер может отвечать сколько угодно.

Два разных «ждёт»: таймаут и взаимоблокировка

Когда код ждёт чужую блокировку, это выглядит одинаково — всё висит. А поломки за этим стоят две разные, и лечатся они по-разному.

Конфликт блокировок. Один сеанс держит данные, второй ждёт своей очереди и не дожидается: платформа ждёт ограниченное время и сдаётся с сообщением «Конфликт блокировок при выполнении транзакции превышает максимально допустимое время». Виноват здесь обычно не тот, кто получил ошибку, а тот, кто держал: длинная транзакция, внутри которой читают файл, печатают или ждут ответа чужого сервиса.

Взаимоблокировка. Двое ждут друг друга: первый занял товары и тянется к остаткам, второй занял остатки и тянется к товарам. Ждать можно вечно, поэтому база сама убивает одного из них. Сообщение так и звучит — «обнаружена взаимоблокировка».

Лечение у неё неожиданное: не «блокировать поменьше», а обращаться к таблицам в одном и том же порядке во всём коде. Если оба сначала берут товары, а потом остатки, ждать по кругу становится нечему.

Как отличить одно от другого

ПризнакКонфликт блокировокВзаимоблокировка
Сообщение«превышает максимально допустимое время»«обнаружена взаимоблокировка»
Кто виноваттот, кто ДЕРЖАЛ долгооба — из-за разного порядка
Когда вылезаетпод нагрузкой, на длинных операцияхредко и «случайно», под нагрузкой
Чем лечитсякороткая транзакция, ничего лишнего внутриодинаковый порядок обращения к данным
Что НЕ помогаетувеличить таймаут — просто ждут дольшеповторить операцию — повторится и она

Блокировать надо раньше, чем кажется

Классическая задача: прочитать остаток, проверить, что хватает, и списать. Между чтением и записью успевает вклиниться второй сеанс — и оба спишут последнюю штуку.

Транзакция сама по себе от этого не спасает: чтение данных их не занимает. Нужна управляемая блокировка, поставленная ДО чтения: «эти строки регистра мои до конца транзакции».

Правило звучит так: если по прочитанному будете принимать решение о записи — блокируйте до чтения. Поставленная после, она защищает от того, чего уже не случится.

Оборотная сторона — не блокировать лишнего. Блокировка на весь регистр вместо трёх товаров превращает параллельную работу в очередь, и все ждут одного.

9. Проверять на объёме, а не на трёх строках

Все ошибки этой главы объединяет одно: на разработке их не видно. В тестовой базе двадцать документов, и любой запрос отвечает мгновенно.

Поэтому на боевую базу уезжает код, который никто не проверял на боевых объёмах, — и первым его тестировщиком оказывается бухгалтер в последний день квартала.

Что с этим делают: копию боевой базы с обезличенными данными, замер на ней, и простой вопрос к себе перед сдачей — «сколько строк тут будет через год».

Как искать, а не гадать

Замер производительности в конфигураторе показывает, какие строки сколько заняли. Смотреть надо не на сумму, а на строку с наибольшим числом вызовов: обычно виновата она.

Технологический журнал и журнал запросов СУБД показывают, что на самом деле ушло в базу. Там же видно дописанное RLS условие, которого нет в тексте запроса.

Правило, которое экономит часы: сначала посчитайте, сколько раз. Медленный код почти всегда не сложный, а повторённый.

Чем мерить, когда замера мало

Замер производительности показывает свои строки кода. Этого хватает, пока тормозит код. Когда тормозит платформа или база, замер честно скажет «здесь три секунды» — и не скажет почему.

Технологический журнал — настройка платформы, после которой сервер пишет в файлы всё: длительные запросы, конфликты блокировок, взаимоблокировки с их участниками, обращения к СУБД. Это единственный способ узнать, кто именно кого держал полчаса назад. Включают его точечно и ненадолго: файлов он пишет много.

Журнал регистрации отвечает на другой вопрос — «что вообще происходило»: кто вошёл, что провёл, где получил ошибку. Первое место, куда смотрят по жалобе «вчера в обед всё встало».

Консоль кластера показывает живое: сколько сеансов, какие из них сейчас выполняют запрос, сколько памяти заняли рабочие процессы. Отсюда видно «висит один» против «плохо всем».

Сначала померить, потом чинить

Порядок, который экономит дни: воспроизвести, померить, починить одно, померить снова.

Без первого замера непонятно, что чинить: интуиция про производительность ошибается почти всегда, и оптимизируют обычно то место, которое занимает три процента времени.

Без второго непонятно, помогло ли. «Стало вроде быстрее» после пяти правок разом — это не результат, а ощущение, и в следующий раз придётся начинать сначала.

Два приёма, о которых узнают поздно

Повторное использование возвращаемых значений. У общего модуля есть такое свойство. Включили — и платформа запоминает результат функции для тех же параметров на время вызова или сеанса. Для справочных данных, которые спрашивают сто раз за операцию — курс валюты, настройка, реквизит организации, — это снимает сотню обращений к базе одной галочкой.

Цена: значение может устареть, и функция обязана быть честной — при одних и тех же параметрах возвращать одно и то же. Класть туда «текущие остатки» нельзя.

Меньше походов на сервер. Каждый вызов сервера с клиента — это дорога по сети, и на плохой связи она стоит больше, чем сам расчёт. Три вызова подряд, каждый за одним значением, надо собрать в один, возвращающий структуру.

И обратное: не тащить на клиент лишнее. Таблица на десять тысяч строк, которую потом показывают первыми двадцатью, едет по сети целиком.

Упражнение 1. Сколько походов в базу

Функция ПоходовВБазу(Строк, ВЦикле) считает походы в базу. Если ВЦикле истина — по походу на строку. Если ложь — один поход на все строки. Строк ноль — ноль походов в обоих случаях.

Решается в тренажёре: код запускается и проверяется сразу.

Упражнение 2. Кто виноват в ожидании

Функция ВиноватДержавший(Сообщение) получает текст ошибки: "Конфликт блокировок при выполнении транзакции" или "Обнаружена взаимоблокировка". Вернуть Истина, если виноват тот, кто ДЕРЖАЛ данные долго, и Ложь, если виноваты обе стороны разным порядком обращения к данным.

Решается в тренажёре: код запускается и проверяется сразу.

Короткий свод

Девять причин, и первые пять видны прямо в тексте кода: запрос в цикле, отбор в ГДЕ вместо параметров, соединение с подзапросом, условие без индекса, обращение через точку в цикле. Остальные видны только под нагрузкой: один тяжёлый запрос вместо пакета, ожидание чужой блокировки, проверка на трёх строках вместо объёма.

Замер показывает не самую медленную строку, а самую повторяемую — искать надо её. Медленный код почти всегда не сложный, а повторённый. А когда замера мало, отвечают технологический журнал и консоль кластера: «висит один» и «плохо всем» лечатся по-разному.

Как закрепить

Читать про код и писать код — разные умения. В тренажёре к этой главе идут упражнения: вы пишете решение, оно запускается на настоящем интерпретаторе 1С и проверяется сразу, без установки платформы.

Открыть эту главу в тренажёре

Виртуальные таблицы регистров
СКД: схема, настройки, ресурсы