Оптимизация запросов в 1С за 7 шагов: индексы и СКД

Почему оптимизация запросов в 1С — это не роскошь, а необходимость
Медленные запросы в 1С — одна из главных причин, по которым пользователи жалуются на «тормозящую» систему. Когда отчёт формируется пять минут, а проведение документа вызывает таймаут, бизнес теряет деньги и нервные клетки. При этом большинство проблем с производительностью решается без дорогостоящего апгрейда железа — достаточно грамотно переписать запросы.
Семь шагов ускорения запросов в 1С: замер через технологический журнал, индексы (включая ИНДЕКСИРОВАТЬ ПО для временных таблиц), управляемые блокировки, отказ от запросов в цикле и функций в ГДЕ, условия внутрь виртуальных таблиц, настройка СКД и чтение плана запроса в СУБД. Плюс два блока, о которых забывают: скрытые тормоза (RLS, итоги регистров, составные типы, обращение через точку) и порционная обработка больших объёмов через фоновые задания.
Профилирование, индексы, управляемые блокировки, чистые запросы, виртуальные таблицы и грамотная СКД — база для ускорения. Но чтобы разобраться, почему запрос всё ещё тормозит, нужно уметь читать план запроса СУБД и понимать, когда 1С делает лишний обход таблицы. Ниже — как это делать и как удержать результат надолго.
В этой статье мы разберём 7 конкретных шагов, которые позволят вам систематически улучшить производительность запросов в 1С:Предприятие 8. Каждый шаг сопровождается рабочим кодом, объяснением механизма и типичными ошибками, которых следует избегать. Подход применим как к конфигурациям на платформе 8.3, так и к доработкам в задачах по 1С:ERP и других тяжёлых решениях.
Важно: перед любой оптимизацией измерьте текущее время выполнения запросов с помощью инструментов замера производительности. Без базовых метрик невозможно оценить эффект изменений.
Шаг 1. Профилирование — найдите «узкое место» до начала оптимизации
Оптимизировать вслепую — всё равно что лечить пациента без диагноза. Первый шаг — выявить конкретные запросы, которые тормозят систему. В 1С для этого есть несколько инструментов.
Замер производительности встроенными средствами
Используйте объект ЗамерВремени и технологический журнал. Для быстрой диагностики в коде можно использовать следующий подход:
// Замер времени выполнения запроса
Процедура ЗамерВременниЗапроса()
// Фиксируем время начала
ВремяНачала = ТекущаяДатаСекунда();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура,
| СУММА(Продажи.КоличествоОборот) КАК КоличествоОборот
|ИЗ
| РегистрНакопления.Продажи.Обороты(
| &НачалоПериода,
| &КонецПериода,
| День,
| ) КАК Продажи
|СГРУППИРОВАТЬ ПО
| Продажи.Номенклатура";
Запрос.УстановитьПараметр("НачалоПериода", НачалоГода(ТекущаяДата()));
Запрос.УстановитьПараметр("КонецПериода", КонецГода(ТекущаяДата()));
Результат = Запрос.Выполнить();
// Считаем время выполнения
ВремяВыполнения = ТекущаяДатаСекунда() - ВремяНачала;
Сообщить("Запрос выполнен за: " + ВремяВыполнения + " сек.");
КонецПроцедуры
Технологический журнал
Для системного анализа настройте технологический журнал с фильтром по событию DBMSSQL (или DBPOSTGRS для PostgreSQL). Это позволит увидеть реальные SQL-запросы, которые 1С отправляет на сервер БД, и найти те, что выполняются дольше заданного порога. Рекомендуемый порог для начала анализа — 1000 мс.
После профилирования у вас будет список «виновников». Именно с ними мы будем работать на следующих шагах.
Шаг 2. Индексы в 1С — правильная настройка без лишних накладных расходов
Индексы — самый мощный инструмент ускорения запросов. Но у них есть обратная сторона: каждый дополнительный индекс замедляет запись данных и увеличивает размер базы. Поэтому важно добавлять только те индексы, которые реально используются.
Индексирование реквизитов объектов метаданных
В свойствах реквизита справочника, документа или регистра есть флаг «Индексировать». Устанавливайте его для полей, которые:
- Используются в условиях отбора (секция ГДЕ запроса);
- Участвуют в соединениях таблиц (секция СОЕДИНЕНИЕ ... ПО);
- Применяются в сортировке больших выборок (УПОРЯДОЧИТЬ ПО).
Составные индексы в регистрах
Для регистров накопления и сведений особенно важен порядок полей в составном индексе. Правило «левого префикса»: индекс по полям (А, Б, В) ускорит запросы с условием по А, по А+Б, по А+Б+В, но не ускорит запрос только по Б или В.
// Пример запроса, который выиграет от составного индекса
// по полям (Номенклатура, Склад, Период)
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &МоментВремени,
| Номенклатура = &Номенклатура
| И Склад = &Склад
| ) КАК Остатки";
Индексирование временных таблиц
Если вы используете временные таблицы в пакетных запросах, обязательно индексируйте их поля, по которым происходит соединение. Это делается директивой ИНДЕКСИРОВАТЬ ПО:
// Создание временной таблицы с индексом для ускорения соединений
Запрос.Текст =
"ВЫБРАТЬ
| Документы.Ссылка КАК ДокументСсылка,
| Документы.Контрагент
|ПОМЕСТИТЬ ВТ_Документы
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документы
|ГДЕ
| Документы.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Документы.Проведен = ИСТИНА
|ИНДЕКСИРОВАТЬ ПО
| ДокументСсылка,
| Контрагент
|;
|
|// Второй запрос пакета использует индексированную временную таблицу
|ВЫБРАТЬ
| ВТ_Документы.Контрагент,
| ТаблицаТоваров.Номенклатура,
| СУММА(ТаблицаТоваров.Количество) КАК ИтогоКоличество
|ИЗ
| ВТ_Документы КАК ВТ_Документы
| ВНУТРЕННЕЕ СОЕДИНЕНИЕ Документ.РеализацияТоваровУслуг.Товары КАК ТаблицаТоваров
| ПО ВТ_Документы.ДокументСсылка = ТаблицаТоваров.Ссылка
|СГРУППИРОВАТЬ ПО
| ВТ_Документы.Контрагент,
| ТаблицаТоваров.Номенклатура";
Шаг 3. Управление блокировками — от тупиков к параллельной работе
Блокировки — второй по значимости источник проблем с производительностью. В многопользовательской среде неправильно настроенные блокировки приводят к взаимным ожиданиям и, в худшем случае, к дедлокам. Особенно актуально для 1С:Бухгалтерия, где одновременно работают десятки пользователей.
Управляемые блокировки vs. автоматические
В современных конфигурациях рекомендуется использовать управляемый режим блокировок. Он даёт разработчику контроль над тем, когда и на что накладываются блокировки, в отличие от автоматического режима, который блокирует данные «на всякий случай».
// Пример использования управляемых блокировок
Процедура ПровестиДокументСБлокировкой(ДокументСсылка)
НачатьТранзакцию();
Попытка
// Устанавливаем блокировку только на нужные данные
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить(
"РегистрНакопления.ТоварыНаСкладах");
ЭлементБлокировки.УстановитьЗначение(
"Номенклатура",
ДокументСсылка.Номенклатура);
ЭлементБлокировки.УстановитьЗначение(
"Склад",
ДокументСсылка.СкладОтправитель);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
Блокировка.Заблокировать();
// Выполняем операции с данными
ДокументОбъект = ДокументСсылка.ПолучитьОбъект();
ДокументОбъект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
КонецПроцедуры
Чтение данных без блокировки
Для запросов, которые только читают данные (например, для построения отчётов), используйте подсказку РАЗРЕШЕННЫЕ и избегайте чтения в транзакции без необходимости. Это кардинально снижает конкуренцию за ресурсы:
// Запрос для отчёта — читаем без лишних блокировок
Запрос.Текст =
"ВЫБРАТЬ РАЗРЕШЕННЫЕ
| Контрагенты.Наименование,
| Взаиморасчеты.СуммаОстаток
|ИЗ
| РегистрНакопления.ВзаиморасчетыСКонтрагентами.Остатки КАК Взаиморасчеты
| ЛЕВОЕ СОЕДИНЕНИЕ Справочник.Контрагенты КАК Контрагенты
| ПО Взаиморасчеты.Контрагент = Контрагенты.Ссылка
|ГДЕ
| Взаиморасчеты.СуммаОстаток <> 0";
Шаг 4. Переписываем запросы — типичные антипаттерны и их замена
Даже опытные разработчики допускают ошибки в написании запросов, которые незаметны на малых объёмах данных, но катастрофически сказываются на производительности при росте базы.
Антипаттерн 1: Функции в условии ГДЕ
Применение функций к полям в секции ГДЕ делает использование индекса невозможным. СУБД вынуждена перебрать все строки таблицы.
// ПЛОХО: функция ГОД() не позволяет использовать индекс по полю Дата
// ГДЕ ГОД(Документы.Дата) = 2024
// ХОРОШО: используем диапазон дат — индекс работает
Запрос.Текст =
"ВЫБРАТЬ
| Документы.Ссылка,
| Документы.Дата,
| Документы.Сумма
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документы
|ГДЕ
| Документы.Дата >= &НачалоПериода
| И Документы.Дата <= &КонецПериода
| И Документы.Проведен = ИСТИНА";
Антипаттерн 2: Запросы в цикле
Это, пожалуй, самая распространённая и разрушительная ошибка. Каждое обращение к базе данных имеет накладные расходы. Тысяча запросов в цикле — это тысячекратные накладные расходы.
// ПЛОХО: запрос внутри цикла
// Для каждого документа — отдельный запрос к базе!
Для Каждого СтрокаТаблицы Из ТаблицаДокументов Цикл
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ ... ГДЕ Документ.Ссылка = &Ссылка";
Запрос.УстановитьПараметр("Ссылка", СтрокаТаблицы.Ссылка);
// Это катастрофа производительности!
КонецЦикла;
// ХОРОШО: один запрос для всех документов с использованием списка значений
СписокСсылок = Новый СписокЗначений;
Для Каждого СтрокаТаблицы Из ТаблицаДокументов Цикл
СписокСсылок.Добавить(СтрокаТаблицы.Ссылка);
КонецЦикла;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Документы.Ссылка,
| Документы.Сумма,
| Документы.Контрагент
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Документы
|ГДЕ
| Документы.Ссылка В (&СписокСсылок)";
Запрос.УстановитьПараметр("СписокСсылок", СписокСсылок);
Результат = Запрос.Выполнить();
Антипаттерн 3: Избыточные соединения через ОБЪЕДИНИТЬ
Оператор ОБЪЕДИНИТЬ ВСЕ работает быстрее, чем ОБЪЕДИНИТЬ, потому что не выполняет дедупликацию строк. Используйте ОБЪЕДИНИТЬ только тогда, когда дубликаты действительно нужно убрать.
Шаг 5. Виртуальные таблицы и параметры периода — используйте правильно
Виртуальные таблицы (Остатки, Обороты, ОстаткиИОбороты) — мощный инструмент 1С, но их неправильное использование приводит к огромным нагрузкам на СУБД.
Передавайте условия внутрь виртуальной таблицы
Критически важное правило: условия отбора по измерениям регистра нужно передавать в параметры виртуальной таблицы, а не в секцию ГДЕ внешнего запроса. Во втором случае система сначала развернёт все данные таблицы, а потом отфильтрует — это катастрофически медленно.
// ПЛОХО: условие снаружи — сначала читаем ВСЕ остатки, потом фильтруем
// ЭТО ОЧЕНЬ МЕДЛЕННО НА БОЛЬШИХ БАЗАХ!
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки КАК Остатки
|ГДЕ
| Остатки.Склад = &Склад";
// ХОРОШО: условие внутри параметров виртуальной таблицы
// СУБД сразу читает только нужные данные
Запрос.Текст =
"ВЫБРАТЬ
| Остатки.Номенклатура,
| Остатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ТоварыНаСкладах.Остатки(
| &МоментВремени,
| Склад = &Склад
| ) КАК Остатки";
Запрос.УстановитьПараметр("МоментВремени", МоментВремени);
Запрос.УстановитьПараметр("Склад", ВыбранныйСклад);
Выбор между Остатки и ОстаткиИОбороты
Если вам нужны только остатки — используйте .Остатки(). Таблица .ОстаткиИОбороты() делает значительно больше работы и нужна только тогда, когда вам одновременно нужны и остатки, и обороты за период. Не берите лишнего.
Шаг 6. Оптимизация СКД — настройка схемы компоновки данных для скорости
Система компоновки данных — удобный инструмент для построения отчётов, но без должной настройки СКД-отчёты могут работать медленнее, чем аналогичные запросы, написанные вручную. Разберём ключевые точки оптимизации.
Ограничение выбираемых полей
По умолчанию СКД может выбирать из базы данных больше полей, чем нужно для конкретного варианта отчёта. Используйте настройки доступных полей и явно ограничивайте набор выбираемых данных в зависимости от варианта отчёта.
Правильное использование параметров СКД
// Программная настройка СКД с оптимальными параметрами
Процедура СформироватьОтчетСКД(НачалоПериода, КонецПериода, Склад)
// Получаем схему компоновки
Схема = РеквизитФормыВЗначение("СхемаКомпоновкиДанных");
// Создаём настройки компоновки
Настройки = Схема.НастройкиПоУмолчанию;
// Устанавливаем параметры отбора
ДляПараметра = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(
Новый ПараметрКомпоновкиДанных("НачалоПериода"));
ДляПараметра.Значение = НачалоПериода;
ДляПараметра.Использование = Истина;
ДляПараметра = Настройки.ПараметрыДанных.НайтиЗначениеПараметра(
Новый ПараметрКомпоновкиДанных("КонецПериода"));
ДляПараметра.Значение = КонецПериода;
ДляПараметра.Использование = Истина;
// Добавляем отбор по складу для уменьшения объёма данных
ЭлементОтбора = Настройки.Отбор.Элементы.Добавить(
Тип("ЭлементОтбораКомпоновкиДанных"));
ЭлементОтбора.ЛевоеЗначение = Новый ПолеКомпоновкиДанных("Склад");
ЭлементОтбора.ВидСравнения = ВидСравненияКомпоновкиДанных.Равно;
ЭлементОтбора.ПравоеЗначение = Склад;
ЭлементОтбора.Использование = Истина;
// Компонуем и выводим результат
Компоновщик = Новый КомпоновщикМакетаКомпоновкиДанных;
Макет = Компоновщик.Выполнить(Схема, Настройки, , , Тип("ГенераторМакетаКомпоновкиДанныхДляКоллекцийЗначений"));
Процессор = Новый ПроцессорКомпоновкиДанных;
Процессор.Инициализировать(Макет, , , Истина);
КонецПроцедуры
Наборы данных в СКД: объединение vs. соединение
В СКД можно связывать несколько наборов данных. Связь типа «Объединение» выполняется на уровне СУБД (быстро), а связь типа «Соединение» — на уровне платформы 1С (медленно, особенно на больших объёмах). Всегда предпочитайте один хорошо написанный запрос нескольким наборам данных с соединением на уровне СКД.
Для сложных аналитических отчётов, которые нужны в электронном документообороте или при интеграциях, особенно важно контролировать количество обращений к базе данных из СКД.
Найдите специалиста для решения этой задачи на koderion.ru
Шаг 7. Читаем план запроса — где 1С делает лишний обход таблицы
Индексы и правильный запрос — половина дела. Вторая половина — убедиться, что СУБД реально использует то, что вы задумали. Для этого смотрят план выполнения запроса на уровне SQL Server или PostgreSQL.
Как получить SQL-текст запроса из 1С
1С не показывает SQL напрямую. Включите технологический журнал с событием DBMSSQL и свойством Sql — в лог попадёт готовый SQL-текст с временем выполнения. Скопируйте этот текст в SQL Server Management Studio (или в консоль PostgreSQL) и запросите план.
- В SSMS нажмите Ctrl+M («Include Actual Execution Plan») и выполните запрос.
- В PostgreSQL добавьте перед запросом
EXPLAIN (ANALYZE, BUFFERS).
Что искать в плане
Три главных красных флага:
- Table Scan / Seq Scan — СУБД читает таблицу целиком. Значит, индекс не используется или его нет. Проверьте: не применяете ли вы функцию к полю в ГДЕ (см. шаг 4), совпадает ли порядок полей с составным индексом (шаг 2).
- Key Lookup / RID Lookup — индекс нашёл строки, но за остальными полями лезет в основную таблицу по каждой строке. На выборке в десятки тысяч строк это тысячи дополнительных обращений. Решение — добавить недостающие поля в индекс (покрывающий индекс) или сузить выборку.
- Hash Match с большим объёмом — соединение больших таблиц без подходящего индекса. Часто лечится индексированием поля соединения или временной таблицей с
ИНДЕКСИРОВАТЬ ПО.
Смотрите на «толщину» стрелок
В графическом плане SSMS толщина стрелки = количество строк. Если из таблицы вылетает жирная стрелка, а после фильтра остаётся тонкая — фильтр применяется слишком поздно. Это тот самый случай, когда условие нужно переносить внутрь виртуальной таблицы или ближе к источнику данных.
Практический критерий: если оценочное (estimated) число строк в плане отличается от фактического (actual) в разы — статистика СУБД устарела. Обновите её (UPDATE STATISTICS в MS SQL, ANALYZE в PostgreSQL). Иногда одно это ускоряет запрос кратно без переписывания кода.
Мини-чеклист разбора одного медленного запроса
- Достали SQL из техжурнала.
- Сняли план (актуальный, не оценочный).
- Нашли самый «дорогой» оператор (по проценту от стоимости).
- Определили причину: скан, lookup, устаревшая статистика или соединение без индекса.
- Внесли изменение в конфигурацию 1С (индекс, переписанный запрос) — не в SQL напрямую.
- Повторили замер. Записали до/после.
Как удержать результат: чтобы запросы не «поехали» снова через месяц
Оптимизация запросов в 1С — не разовая акция. База растёт, справочники распухают, разработчики добавляют новый код. Запрос, который сегодня отрабатывает за 0,3 секунды на 50 тысячах строк, через полгода на миллионе строк может уйти в таймаут. Разовые правки без контроля откатываются.
Заведите порог и автоматический сбор долгих запросов
Оставьте технологический журнал включённым постоянно с фильтром по длительности (например, всё, что дольше 3 секунд). Раз в неделю просматривайте топ-10 самых долгих запросов. Так вы ловите деградацию до того, как позвонят пользователи.
Регламентное обслуживание СУБД
Настройте по расписанию (обычно ночью):
- Обновление статистики — иначе планировщик СУБД принимает решения по устаревшим данным.
- Реиндексацию / перестроение индексов при высокой фрагментации.
- В связке с 1С — своевременное закрытие периодов и свёртку регистров, чтобы виртуальные таблицы не пересчитывали годы истории.
Фиксируйте цифры «до и после»
Заведите простую таблицу: имя запроса, время до правки, время после, дата, что изменили. Через полгода это ваша страховка: видно, какой запрос снова начал тормозить и что с ним уже делали. Без замеров любая «оптимизация» превращается в спор на ощущениях.
Ревью запросов при новых доработках
Договоритесь в команде о простых правилах на code review: никаких запросов в цикле, условия — внутрь виртуальных таблиц, соединения — по индексированным полям. Проверить это дешевле на этапе разработки, чем ловить деградацию на проде.
Запрос переписан, индексы стоят — а всё равно медленно. Четыре скрытых причины
Бывает так: убрали запрос из цикла, перенесли условия в виртуальную таблицу, повесили индексы — а отчёт как формировался минуту, так и формируется. Значит, тормозит не текст запроса, а то, что платформа добавляет к нему сама. Вот что проверять по порядку.
1. RLS — ограничение доступа на уровне записей
Платформа подмешивает условия проверки прав в каждый пользовательский запрос. Пользователь видит один запрос, СУБД получает совсем другой — с дополнительными соединениями к регистрам ограничения доступа.
Как проверить за пять минут: выполните тот же отчёт под пользователем с ролью «Полные права» (RLS для него не отрабатывает) и сравните время. Разница в разы — виноват RLS. Дальше два пути: упростить шаблон ограничения доступа или переписать отчёт так, чтобы тяжёлая часть выборки шла по регистрам, на которые RLS не наложен.
2. Итоги регистров накопления не рассчитаны
Виртуальная таблица .Остатки() на произвольную дату быстрая ровно до тех пор, пока эта дата попадает в рассчитанные итоги. Если итоги не рассчитаны или граница расчёта осталась в прошлом году, СУБД суммирует движения за весь период — построчно.
Проверьте в конфигураторе и в режиме предприятия:
- включено ли использование итогов у регистра;
- до какого периода итоги рассчитаны (перерасчёт итогов ставят в регламентные задания);
- нужно ли включить разделение итогов — оно снимает конкуренцию при параллельной записи движений.
3. Соединения и обращения по полям составного типа
Поле составного типа (Регистратор, Владелец, ссылка с несколькими возможными типами) хранится в базе как набор колонок. Соединение по такому полю СУБД разворачивает в несколько соединений — по одному на каждый возможный тип. Если у регистратора двадцать типов документов, вы получаете двадцать джойнов вместо одного.
// ПЛОХО: обращение через точку по полю составного типа
// Движения.Регистратор.Контрагент — платформа соединит регистр
// со ВСЕМИ типами документов-регистраторов
// ХОРОШО: явно приводим тип — одно соединение вместо десятков
Запрос.Текст =
"ВЫБРАТЬ
| Движения.Регистратор КАК Регистратор,
| ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг).Контрагент КАК Контрагент,
| Движения.Количество
|ИЗ
| РегистрНакопления.ТоварыНаСкладах КАК Движения
|ГДЕ
| Движения.Период МЕЖДУ &НачалоПериода И &КонецПериода
| И ВЫРАЗИТЬ(Движения.Регистратор КАК Документ.РеализацияТоваровУслуг) ЕСТЬ НЕ NULL";То же правило для условий: сравнение поля составного типа со значением одного типа лучше оборачивать в ВЫРАЗИТЬ.
4. Обращение через точку в выборке и в динамических списках
Каждая точка в запросе — это неявное ЛЕВОЕ СОЕДИНЕНИЕ. Три точки подряд (Документ.Контрагент.ОсновнойДоговор.Валюта) — три соединения. На выборке в сто строк незаметно, на выборке в сто тысяч — минуты.
Отдельная боль — обращение через точку в цикле обхода выборки. Формально запрос один, но платформа за каждым реквизитом лезет в базу отдельно:
// ПЛОХО: реквизит через точку внутри цикла — запрос на каждой итерации
Пока Выборка.Следующий() Цикл
Наименование = Выборка.Ссылка.Контрагент.НаименованиеПолное; // обращение к БД
КонецЦикла;
// ХОРОШО: забрали нужное поле сразу в запросе
Запрос.Текст =
"ВЫБРАТЬ
| Док.Ссылка КАК Ссылка,
| Док.Контрагент.НаименованиеПолное КАК НаименованиеКонтрагента
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода";В динамических списках форм проверьте то же самое: произвольный запрос с точками в колонках, отбор по неиндексируемому реквизиту и вычисляемые поля превращают открытие списка документов в полноценный тяжёлый отчёт. Список читает данные при каждой прокрутке.
Порядок диагностики
- Замерили под обычным пользователем и под полными правами — отсекли RLS.
- Посмотрели, рассчитаны ли итоги регистра на нужную дату.
- Прошли по тексту запроса и посчитали точки и поля составных типов.
- Если запрос чистый, а SQL из техжурнала всё равно огромный — причина в одном из трёх пунктов выше.
Как обработать миллион строк и не положить сервер
Перепроведение документов за год, пересчёт себестоимости, разовая обработка данных после переноса — здесь оптимизация запросов в 1С работает по другим правилам. Задача не «выполнить запрос быстрее», а «выполнить его так, чтобы не съесть всю память сервера и не заблокировать работающих пользователей».
Не выгружайте всё в одну таблицу значений
Результат запроса на миллион строк, выгруженный в таблицу значений на сервере, живёт в оперативной памяти рабочего процесса. Один такой сеанс способен уронить производительность всего кластера. Обрабатывайте порциями.
Курсор по ссылке — рабочий шаблон порционной обработки
Классическая ошибка — использовать ПЕРВЫЕ 1000 со смещением по номеру строки: 1С так не умеет, а перебор через отбор по дате даёт дубли и пропуски. Надёжный приём — двигаться по возрастанию ссылки, запоминая последнюю обработанную.
// Порционная обработка: по 1000 документов за проход,
// каждая порция — отдельная транзакция
Процедура ОбработатьДокументыПорциями(НачалоПериода, КонецПериода)
ПоследняяСсылка = Документы.РеализацияТоваровУслуг.ПустаяСсылка();
ВсегоОбработано = 0;
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Док.Ссылка КАК Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг КАК Док
|ГДЕ
| Док.Ссылка > &ПоследняяСсылка
| И Док.Дата МЕЖДУ &НачалоПериода И &КонецПериода
| И Док.Проведен = ИСТИНА
|УПОРЯДОЧИТЬ ПО
| Ссылка";
Запрос.УстановитьПараметр("НачалоПериода", НачалоПериода);
Запрос.УстановитьПараметр("КонецПериода", КонецПериода);
Пока Истина Цикл
Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылка);
ТаблицаПорции = Запрос.Выполнить().Выгрузить();
Если ТаблицаПорции.Количество() = 0 Тогда
Прервать;
КонецЕсли;
Для Каждого Строка Из ТаблицаПорции Цикл
НачатьТранзакцию();
Попытка
Объект = Строка.Ссылка.ПолучитьОбъект();
Объект.Записать(РежимЗаписиДокумента.Проведение);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
// Не прерываем всю обработку из-за одного документа
ЗаписьЖурналаРегистрации("Перепроведение",
УровеньЖурналаРегистрации.Ошибка, , Строка.Ссылка,
ОписаниеОшибки());
КонецПопытки;
КонецЦикла;
ПоследняяСсылка = ТаблицаПорции[ТаблицаПорции.Количество() - 1].Ссылка;
ВсегоОбработано = ВсегоОбработано + ТаблицаПорции.Количество();
// Прогресс в журнал — чтобы видеть, что обработка жива
ЗаписьЖурналаРегистрации("Перепроведение",
УровеньЖурналаРегистрации.Информация, , ,
"Обработано: " + ВсегоОбработано);
КонецЦикла;
КонецПроцедурыЧто здесь важно и почему:
- Отбор по
Ссылка > &ПоследняяСсылкаидёт по кластерному индексу таблицы — выборка следующей порции остаётся быстрой на любом проходе, хоть на первом, хоть на пятисотом. - Транзакция на документ, а не на всю обработку. Одна транзакция на миллион записей — это гигантский журнал транзакций СУБД, эскалация блокировок и заблокированные пользователи.
- Ошибка одного документа не убивает обработку. Пишем в журнал регистрации и идём дальше, разбираем проблемные документы отдельным списком.
- Прогресс в журнале. Иначе через два часа невозможно понять, обработка работает или зависла.
Фоновые задания и распараллеливание
Длинную обработку запускайте фоновым заданием, а не в клиентском сеансе: клиент отвалится по таймауту, а задание доработает. Если сервер позволяет — разбейте работу на несколько параллельных заданий по непересекающимся диапазонам данных: по складам, по организациям, по остатку от деления кода на число потоков.
Два ограничения, о которых забывают. Первое: параллельные потоки не должны трогать одни и те же записи регистров — иначе вы получите взаимные блокировки вместо ускорения. Второе: число потоков имеет смысл соотносить с количеством ядер сервера и лицензий; десять потоков на слабой машине работают медленнее трёх.
Когда запускать
Тяжёлые массовые обработки — только в окно с минимальной пользовательской активностью, обычно ночью. Перед запуском на боевой базе прогоните на копии и засеките время на 1000 записей: умножив на объём, вы получите реалистичную оценку и поймёте, влезаете ли в окно.