Быстрая обработка 1С: замер, запросы, порции и фон

· 11 мин

Содержание

Правильная обработка в 1С строится по одному принципу: данные читаются одним запросом, а не в цикле. Пишутся они порциями в коротких транзакциях, а всё, что идёт дольше минуты, уходит в фоновое задание. Сначала замер, потом оптимизация. Без замера легко потратить день на ускорение кода, который занимает 2% времени.

Подход одинаков для внешней обработки .epf, кода в расширении и самой конфигурации. Примеры написаны для платформы 8.3 и управляемых форм.

С чего начать: найти, где обработка теряет время

Медленная обработка почти никогда не тормозит «вся». Обычно виноваты 1-3 строки: запрос в цикле, обращение к реквизиту ссылки, запись объекта с тяжёлым обработчиком. Задача первого шага — найти именно их.

Замер производительности в отладчике

  1. Откройте конфигурацию в Конфигураторе и запустите отладку (F5).
  2. Включите Отладка → Замер производительности.
  3. Выполните обработку на типичном объёме данных.
  4. Выключите замер. Откроется таблица: модуль, строка, число вызовов, время и процент.

Сортируйте по проценту. Строка с 40-60% времени — первый кандидат. Смотрите и на колонку количества: строка, вызванная 120 000 раз, подсказывает, что внутри цикла сидит обращение к базе.

💡 Замер ведите на копии базы с реальным объёмом. На демо-базе из 300 документов любой код быстрый, а проблемы видны только на сотнях тысяч строк.

Технологический журнал

Если медленно работает не ваш код, а платформа или СУБД (блокировки, долгие SQL-запросы), нужен технологический журнал. Он включается файлом logcfg.xml в каталоге conf установленной платформы. Полезные события:

  • 🔎 DBMSSQL / DBPOSTGRS — запросы к СУБД с длительностью;
  • 🔒 TLOCK, TTIMEOUT, TDEADLOCK — управляемые блокировки, таймауты и взаимоблокировки;
  • ⏱ CALL — серверные вызовы и их длительность.

Журнал без фильтра по длительности за час вырастает до гигабайтов. Ставьте отбор (например, события дольше 1 секунды) и ограничение срока хранения в logcfg.xml.

Простой замер в коде

Для быстрой проверки «до/после» хватает разницы времени в миллисекундах:

Начало = ТекущаяУниверсальнаяДатаВМиллисекундах();
// проверяемый фрагмент
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Начало;
Сообщить(СтрШаблон("Фрагмент выполнен за %1 мс", Формат(Длительность, "ЧГ=0")));

Оптимизация запросов и выборок: читать данные одним запросом

Главная причина медленных обработок — работа с объектами там, где нужен запрос. Каждое обращение «через точку» к реквизиту ссылки (Документ.Контрагент.ИНН) — отдельное чтение из базы. В цикле по 50 000 документов это 50 000 лишних обращений.

Как выглядит медленный код

// Медленно: чтение объекта на каждой итерации
Выборка = Документы.РеализацияТоваровУслуг.Выбрать(НачалоПериода, КонецПериода);
Пока Выборка.Следующий() Цикл
	ИНН = Выборка.Ссылка.Контрагент.ИНН;
	ДокОбъект = Выборка.Ссылка.ПолучитьОбъект();
	// ...
КонецЦикла;

Здесь два узких места: разыменование ссылки и ПолучитьОбъект(), который читает документ целиком со всеми табличными частями.

Как то же самое сделать запросом

Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
|	Док.Ссылка КАК Ссылка,
|	Док.Контрагент.ИНН КАК ИНН,
|	Док.СуммаДокумента КАК Сумма
|ИЗ
|	Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
|	Док.Дата МЕЖДУ &Начало И &Конец
|	И Док.Проведен";
Запрос.УстановитьПараметр("Начало", НачалоПериода);
Запрос.УстановитьПараметр("Конец", КонецПериода);

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
	// Выборка.ИНН уже получен, базу не трогаем
КонецЦикла;

Внутри запроса «точка» превращается в соединение таблиц на стороне СУБД. Это одна операция вместо тысяч.

Правила хорошего запроса

  • ✅ Выбирайте только нужные поля. ВЫБРАТЬ * тянет всё, включая хранилища значений.
  • ✅ Отбор по виртуальным таблицам (Остатки, Обороты, СрезПоследних) ставьте в параметры таблицы, а не в ГДЕ. Тогда отбор применяется до расчёта, а не после.
  • ✅ Для составных типов (Регистратор, поля «любой ссылки») используйте ВЫРАЗИТЬ(Поле КАК Документ.ХХХ): без него платформа соединяет поле со всеми возможными таблицами.
  • ✅ Условия ставьте на индексированные поля: измерения регистров, Ссылка, Дата, реквизиты с флагом индексирования.
  • ❌ Не пишите запрос внутри цикла. Соберите ключи в таблицу значений и передайте её одним параметром.

Выборка или выгрузка

СпособКогда подходитНа что смотреть
Результат.Выбрать()Последовательный проход по большому результатуСтроки читаются по мере обхода, память не растёт
Результат.Выгрузить()Нужны поиск, свёртка, повторный проходВесь результат в памяти; на миллионе строк заметно
ВыборкаИерархия / группировки ИТОГИОтчётные данные с подытогамиУдобна, но запрос с ИТОГИ тяжелее

Пакетные запросы и временные таблицы

Когда одной выборки мало (нужно отфильтровать товары, затем посчитать по ним остатки, затем цены), разбивайте работу на пакет. Каждый шаг кладите во временную таблицу.

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
|	Т.Номенклатура КАК Номенклатура
|ПОМЕСТИТЬ ВТ_Товары
|ИЗ
|	&ТаблицаТоваров КАК Т
|
|ИНДЕКСИРОВАТЬ ПО
|	Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
|	Остатки.Номенклатура КАК Номенклатура,
|	Остатки.КоличествоОстаток КАК Количество
|ИЗ
|	РегистрНакопления.ТоварыНаСкладах.Остатки(
|			&Дата,
|			Номенклатура В
|				(ВЫБРАТЬ
|					ВТ.Номенклатура
|				ИЗ
|					ВТ_Товары КАК ВТ)) КАК Остатки";
Запрос.УстановитьПараметр("ТаблицаТоваров", ТаблицаТоваров);
Запрос.УстановитьПараметр("Дата", ДатаОстатков);

Результаты = Запрос.ВыполнитьПакет();
ТаблицаОстатков = Результаты[1].Выгрузить();

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

Что даёт пакет:

  • 📦 один вызов сервера вместо нескольких;
  • 🧩 сложный запрос распадается на простые шаги, которые оптимизатор СУБД планирует лучше;
  • 📇 ИНДЕКСИРОВАТЬ ПО ускоряет соединение с временной таблицей на десятках тысяч строк.

💡 На маленьких временных таблицах (сотни строк) индекс не нужен: время на его построение больше выигрыша.

ВыполнитьПакет() возвращает массив результатов по порядку запросов, счёт с нуля. Запросы ПОМЕСТИТЬ тоже занимают позицию в массиве, поэтому финальная выборка в примере — Результаты[1].

Запись больших объёмов: порции и короткие транзакции

Чтение ускорить проще, чем запись. При записи работают обработчики ПередЗаписью, ОбработкаПроведения, подписки на события, регистрация изменений для обменов. Это нормально, но требует дисциплины.

Транзакция на порцию, а не на всё

Одна транзакция на 100 000 объектов держит блокировки до самого конца, и остальные пользователи ждут. Транзакция на каждый объект — лишние накладные расходы. Компромисс — порции по 200-1000 объектов:

РазмерПорции = 500;
Счетчик = 0;
НачатьТранзакцию();
Попытка
	Для Каждого Строка Из ДанныеДляЗаписи Цикл
		Объект = Строка.Ссылка.ПолучитьОбъект();
		Объект.Комментарий = Строка.НовыйКомментарий;
		Объект.Записать();
		Счетчик = Счетчик + 1;
		Если Счетчик % РазмерПорции = 0 Тогда
			ЗафиксироватьТранзакцию();
			НачатьТранзакцию();
		КонецЕсли;
	КонецЦикла;
	ЗафиксироватьТранзакцию();
Исключение
	ОтменитьТранзакцию();
	ВызватьИсключение;
КонецПопытки;

При ошибке откатится только текущая порция, уже зафиксированные останутся. Поэтому обработка должна уметь продолжить с места остановки: например, выбирать только ещё не изменённые объекты.

Запись регистров одним набором

Для регистров сведений и накопления пишите набор записей целиком, а не по одной записи. Наполните НаборЗаписей нужными строками, установите отбор и вызовите Записать() один раз.

Режим загрузки

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

Режим загрузки пропускает проверку заполнения и бизнес-логику. Используйте его только для переноса уже проверенных данных. Включать его при проведении документов нельзя: движения не сформируются как обычно.

Фоновые задания и асинхронное выполнение

Обработка дольше 20-30 секунд не должна блокировать форму. Пользователь закрывает «зависшее» окно, сеанс обрывается, данные остаются наполовину изменёнными. Выход — запуск на сервере в фоне.

Запуск фонового задания

Метод должен быть экспортным и лежать в серверном общем модуле:

// Общий модуль "ОбработкаДанныхСервер", флаг "Сервер"
Процедура ОбработатьПериод(Начало, Конец, АдресРезультата) Экспорт
	// тяжёлая работа
	ПоместитьВоВременноеХранилище(Итог, АдресРезультата);
КонецПроцедуры
// Вызов на сервере из формы
Параметры = Новый Массив;
Параметры.Добавить(НачалоПериода);
Параметры.Добавить(КонецПериода);
Параметры.Добавить(АдресРезультата);
Задание = ФоновыеЗадания.Выполнить(
	"ОбработкаДанныхСервер.ОбработатьПериод", Параметры, , "Пересчёт за период");
ИдентификаторЗадания = Задание.УникальныйИдентификатор;

Адрес временного хранилища создайте заранее на форме через ПоместитьВоВременноеХранилище(Неопределено, УникальныйИдентификатор). Результат забирайте ПолучитьИзВременногоХранилища() после завершения задания.

Как следить за ходом

На клиенте подключите проверку раз в 2-5 секунд через ПодключитьОбработчикОжидания. В ней на сервере найдите задание ФоновыеЗадания.НайтиПоУникальномуИдентификатору() и посмотрите Состояние: активно, завершено, завершено аварийно или отменено. При аварийном завершении текст ошибки лежит в ИнформацияОбОшибке.

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

Параллельная обработка

Большой объём можно разделить на части (по месяцам, по организациям, по диапазону кодов) и запустить несколько фоновых заданий одновременно. На сервере с 8 ядрами 4 параллельных задания часто сокращают время в 3 раза.

  • ✅ Части не должны пересекаться по данным, иначе задания будут ждать друг друга на блокировках.
  • ✅ Число заданий держите меньше числа ядер: пользователям тоже нужен процессор.
  • ❌ Не запускайте параллельно запись в одни и те же остатки регистра накопления.

💡 В файловой базе фоновые задания тоже работают, но выполняются в процессе клиентского приложения и параллельной выгоды почти не дают. Реальное ускорение распараллеливанием — в клиент-серверном варианте.

Асинхронность на клиенте

Начиная с платформы 8.3.18 на клиенте есть Асинх и Ждать: диалоги и вопросы пользователю пишутся последовательно, без каскада процедур-оповещений. Это упрощает код, но не ускоряет обработку данных. Тяжёлая работа всё равно должна идти на сервере.

Прямой SQL к базе 1С: когда допустимо

Прямые запросы к СУБД (MS SQL, PostgreSQL) соблазняют скоростью. Правило простое: читать можно с оговорками, писать нельзя.

Почему не писать в таблицы 1С напрямую

  • 🧮 Итоги регистров накопления и бухгалтерии хранятся в отдельных таблицах. Изменение движений в обход платформы оставит итоги неверными.
  • 🔁 Обмены не узнают об изменении: запись не попадёт в планы обмена.
  • 🧠 Сервер 1С кэширует данные, и пользователи какое-то время видят старые значения.
  • 📜 Имена таблиц (_Document123, _Fld4567) меняются при реструктуризации, и скрипт перестаёт работать после обновления.

Когда SQL уместен

  1. Чтение для внешней аналитики: выгрузка в хранилище данных, BI-системы. Соответствие имён таблиц объектам конфигурации даёт функция ПолучитьСтруктуруХраненияБазыДанных().
  2. Обслуживание самой СУБД: перестроение индексов, обновление статистики, контроль размера файлов. Это задачи администратора базы, и на скорость 1С они влияют сильнее любых правок кода.
  3. Работа с внешней базой (не 1С): через объект конфигурации «Внешние источники данных» или через Новый COMОбъект("ADODB.Connection") на Windows-сервере.

Регламент обслуживания СУБД (индексы, статистика) запускайте в нерабочее время: перестроение индексов нагружает диски, и пользователи почувствуют замедление.

Архивация данных, свертка базы и журнал регистрации

Даже идеальный код замедляется, если база за 8 лет выросла до 300 ГБ. Обработка, которая читает историю, становится медленнее вместе с историей.

Свертка и сокращение базы

В типовых конфигурациях на БСП есть свертка информационной базы: документы до выбранной даты заменяются вводом остатков. Ищите её в разделе Администрирование → Обслуживание.

Порядок работы:

  1. Сделайте полную выгрузку .dt или резервную копию СУБД. Свертка необратима.
  2. Выполните свертку на копии и сверьте ключевые остатки (счета, склады, взаиморасчёты) на дату свертки.
  3. Только после сверки повторите на рабочей базе.

Альтернатива свертке — архивная копия: старую базу оставить «только для чтения», а в рабочей начать учёт с остатков.

Итоги регистров

Для регистров накопления и бухгалтерии установите период рассчитанных итогов на начало текущего месяца. Тогда платформа считает остатки от ближайшего хранимого итога, а не по всей истории. Настраивается это в обработке «Управление итогами» (в меню Все функции → Стандартные). Там же есть пересчёт итогов, если отчёты расходятся с движениями.

Журнал регистрации

Журнал растёт незаметно: на базе с 50 пользователями он за год занимает десятки гигабайт. Это бьёт по диску и по скорости открытия самого журнала.

  • 🗂 Уровень записи и разделение файлов по периодам задаются в Конфигураторе: Администрирование → Настройка журнала регистрации.
  • ✂️ Старые записи удаляются там же командой сокращения журнала: укажите дату, до которой записи не нужны, и при желании сохраните их в файл.
  • 📁 В клиент-серверной базе журнал лежит на сервере в каталоге кластера (srvinfo\reg_1541\<идентификатор базы>\1Cv8Log).

Журнал нужен для разбора инцидентов: кто и когда изменил документ. Перед сокращением согласуйте срок хранения с бухгалтерией и безопасностью, а архив сохраните в файл.

Частые ошибки при обработке данных

Диагностика: выберите симптом

Ещё три ошибки, которые встречаются в каждой второй самописной обработке:

  • ❌ Выполнение тяжёлой логики в &НаКлиенте: каждый вызов сервера из цикла — сетевой круг. Переносите цикл целиком в &НаСервереБезКонтекста.
  • ❌ Передача на сервер всей формы ради одного параметра. Директива &НаСервере гоняет данные формы туда и обратно, &НаСервереБезКонтекста — нет.
  • ❌ Отсутствие индикатора хода. Пользователь не видит прогресса, считает обработку зависшей и снимает сеанс посреди записи.

Частые вопросы

Внешняя обработка работает медленнее встроенной в конфигурацию?
Нет. Код внешней обработки .epf выполняется так же, как код конфигурации. Скорость определяется запросами и записью, а не способом подключения.
Сколько объектов писать в одной транзакции?
Ориентир — 200-1000 объектов. Меньше — растут накладные расходы, больше — растёт время блокировок для других пользователей. Подберите размер замером на своей базе.
Можно ли ускорить обработку, включив монопольный режим?
Монопольный режим убирает конкуренцию за блокировки, но выгоняет всех пользователей. Для разовой ночной обработки это допустимо, для регулярной — нет: лучше исправить порции и запросы.
Почему запрос в консоли быстрый, а в обработке медленный?
Проверьте параметры: в консоли часто ставят узкий период или отбор, а в обработку приходит пустое значение, и отбор пропадает. Второй вариант — в обработке запрос вызывается в цикле.
Нужно ли переписывать обработку для PostgreSQL?
Код на встроенном языке переписывать не нужно. Но тяжёлые запросы с подзапросами и ИЛИ в условиях на разных СУБД выполняются по-разному, поэтому замер стоит повторить после переезда.