В мире 1С нередка ситуация, когда отчеты, годами работавшие быстро и без нареканий, начинают катастрофически замедляться. Особенно это актуально для информационных баз с большим объемом данных и самописных конфигураций. Давайте разберем одну из таких проблем: медленное формирование отчета, основанного на регистре накопления, и выясним, как ее эффективно решить.
Наш клиент столкнулся с проблемой: отчет, строящийся на основе самодельного регистра накопления ЗаказыПокупателей, с начала 2026 года стал формироваться крайне медленно. Если декабрь 2025 года обрабатывался за секунды, то январь 2026-го мог занять 30-40 минут. Основное замедление происходило при обращении к измерениям регистра накопления, в частности, к измерению СтавкаНДС. При этом конфигурация и регистр являются самописными, а база данных имеет огромные размеры.
Давайте посмотрим, с чем именно столкнулся разработчик и какие шаги он предпринял до поиска решения на форуме:
Контекст проблемы:
ЗаказыПокупателей.СтавкаНДС регистра накопления.Испробованные методы оптимизации автором:
В. Это дало некоторое ускорение для января 2026 года, но февраль по-прежнему обрабатывался очень медленно.СтавкаНДС. Это также лишь частично улучшило ситуацию, но не обеспечило желаемой скорости.Несмотря на предпринятые меры, проблема оставалась актуальной, что указывает на необходимость более глубокого анализа и оптимизации запроса.
При работе с большими объемами данных, особенно в самописных конфигурациях 1С, запросы к виртуальным таблицам регистров накопления (таким как Обороты, Остатки, СрезПоследних) могут значительно замедляться. Давайте выясним основные причины, которые приводят к таким проблемам:
СтавкаНДС или ЗаказПокупателя) планы выполнения запросов, которые ранее были эффективными, могут стать неоптимальными. СУБД может выбрать другой, менее производительный план из-за изменившихся статистик по данным.ВЫБОР КОГДА, агрегатных функций или функций работы с датами, это увеличивает сложность вычислений, которые должны быть выполнены СУБД, что также может сказаться на производительности.В большинстве случаев, когда отчеты на больших объемах данных начинают работать медленно, наиболее эффективным подходом является оптимизация запроса с использованием временных таблиц. Этот метод позволяет нам:
Давайте подробно рассмотрим предложенное и успешно примененное решение, сравнив его с исходным запросом.
Для более полного понимания сути проблемы и эффективности предложенного решения, давайте сначала проанализируем структуру исходного запроса, который работал медленно. Обратите внимание на то, как происходит прямое соединение с виртуальной таблицей РегистрНакопления.ЗаказыПокупателей.Обороты без предварительной фильтрации, а также на сложные вычисления прямо в секции ВЫБРАТЬ. Эти факторы, особенно при большом объеме данных, могли стать причиной низкой производительности.
ВЫБРАТЬ
Док.Ссылка КАК Заказ,
ВЫБОР
КОГДА (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) > ЗаказыПокупателейОбороты.СуммаУпрПриход
ТОГДА ЗаказыПокупателейОбороты.СуммаУпрПриход + ((ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) - ЗаказыПокупателейОбороты.СуммаУпрПриход)
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаУпрПриход - (ЗаказыПокупателейОбороты.СуммаУпрПриход - (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))))
КОНЕЦ КАК СуммаУпрПриход,
ВЫБОР
КОГДА ЗаказыПокупателейОбороты.НДСПриход = 0
ТОГДА ВЫБОР
КОГДА ЗаказыПокупателейОбороты.СтавкаНДС = ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.БезНДС) ИЛИ ЗаказыПокупателейОбороты.СтавкаНДС = ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС0)
ТОГДА ВЫБОР
КОГДА (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) > ЗаказыПокупателейОбороты.СуммаУпрПриход
ТОГДА ЗаказыПокупателейОбороты.СуммаУпрПриход + ((ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) - ЗаказыПокупателейОбороты.СуммаУпрПриход)
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаУпрПриход - (ЗаказыПокупателейОбороты.СуммаУпрПриход - (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))))
КОНЕЦ
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаБезНДСПриход
КОНЕЦ
ИНАЧЕ ВЫРАЗИТЬ((ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход - ЗаказыПокупателейОбороты.НДСПриход) * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))
КОНЕЦ КАК СуммаБезНДСПриход
ИЗ
Документ.ЗаказПокупателя КАК Док
ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ЗаказыПокупателей.Обороты КАК ЗаказыПокупателейОбороты
ПО Док.Ссылка = ЗаказыПокупателейОбороты.ЗаказПокупателя
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК РегКурсы1
ПО Док.ВалютаДокумента = РегКурсы1.Валюта
И (РегКурсы1.Период = НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ))
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК РегКурсы2
ПО (РегКурсы2.Валюта = &ВалютаУпр)
И (РегКурсы2.Период = НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ))
ГДЕ
Док.Проведен
И Док.ДатаОкончания МЕЖДУ &ДатаН И &ДатаК;
Как мы видим, этот запрос напрямую соединяет Документ.ЗаказПокупателя с виртуальной таблицей РегистрНакопления.ЗаказыПокупателей.Обороты. Если регистр накопления содержит очень много записей, а виртуальная таблица Обороты генерирует агрегированные данные по всей его истории без эффективных ограничений, то даже последующие условия в ГДЕ не смогут существенно ускорить процесс, так как основная работа по агрегации уже будет выполнена для избыточного объема данных.
Предложенный и успешно примененный запрос значительно улучшил производительность отчета. Давайте разберем его по шагам, чтобы понять, как достигается такое ускорение.
ТабДок)На первом этапе мы максимально сужаем выборку документов ЗаказПокупателя, выбирая только те, которые соответствуют основным условиям фильтрации по дате и состоянию проведения. Это позволяет нам сразу работать с минимально необходимым набором данных для заказов.
ВЫБРАТЬ
Док.Ссылка КАК Заказ,
Док.ВалютаДокумента КАК ВалютаДокумента,
НАЧАЛОПЕРИОДА(Док.Дата, ДЕНЬ) КАК ДеньДокумента
Поместить ТабДок
ИЗ
Документ.ЗаказПокупателя КАК Док
ГДЕ
Док.Проведен
И Док.ДатаОкончания МЕЖДУ &ДатаН И &ДатаК
ИНДЕКСИРОВАТЬ ПО
Заказ;
Важные моменты:
Поместить ТабДок: Эта конструкция создает временную таблицу с именем ТабДок. Работа с временными таблицами значительно снижает нагрузку на СУБД, так как они хранятся в оперативной памяти или во временных файлах, а не на диске, и исчезают после завершения запроса.ГДЕ Док.Проведен И Док.ДатаОкончания МЕЖДУ &ДатаН И &ДатаК: Мы применяем все основные фильтры по документу на самом раннем этапе. Это гарантирует, что в ТабДок попадут только те заказы, которые действительно относятся к интересующему нас периоду и статусу.ИНДЕКСИРОВАТЬ ПО Заказ: Создание индекса по полю Заказ во временной таблице ТабДок является критически важным. Этот индекс обеспечит максимально быстрое соединение с другими таблицами на последующих шагах.ЗаказыПокупателейОбороты)Здесь происходит основная оптимизация. Вместо того чтобы получать обороты по всему регистру накопления, мы запрашиваем их только для тех заказов, которые уже были отобраны на первом шаге. Это существенно сокращает объем данных, с которым работает виртуальная таблица Обороты.
ВЫБРАТЬ
ЗаказыПокупателейОбороты.ЗаказПокупателя,
ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход,
ЗаказыПокупателейОбороты.СуммаУпрПриход,
ЗаказыПокупателейОбороты.НДСПриход,
ЗаказыПокупателейОбороты.СтавкаНДС,
ЗаказыПокупателейОбороты.СуммаБезНДСПриход
Поместить ЗаказыПокупателейОбороты
ИЗ
РегистрНакопления.ЗаказыПокупателей.Обороты(, , , ЗаказПокупателя В (ВЫБРАТЬ Таб.Заказ ИЗ ТабДок как Таб)) КАК ЗаказыПокупателейОбороты
ИНДЕКСИРОВАТЬ ПО
ЗаказПокупателя;
Важные моменты:
РегистрНакопления.ЗаказыПокупателей.Обороты(, , , ЗаказПокупателя В (ВЫБРАТЬ Таб.Заказ ИЗ ТабДок как Таб)): Это ключевая часть оптимизации. Условие ЗаказПокупателя В (ВЫБРАТЬ Таб.Заказ ИЗ ТабДок как Таб) передается непосредственно как параметр отбора виртуальной таблицы Обороты. Платформа 1С использует этот параметр для построения более эффективного плана выполнения запроса к регистрам, избегая полного сканирования всего регистра. Вместо этого, СУБД будет агрегировать данные только для конкретных заказов из ТабДок, что радикально снижает объем обрабатываемой информации.ИНДЕКСИРОВАТЬ ПО ЗаказПокупателя: Аналогично первому шагу, создание индекса по полю ЗаказПокупателя во временной таблице ЗаказыПокупателейОбороты критически важно для обеспечения высокой скорости финального соединения.На последнем шаге мы соединяем уже отфильтрованные временные таблицы с регистром сведений КурсыВалют и выполняем все необходимые вычисления. Поскольку мы работаем уже с существенно сокращенным объемом данных, этот этап выполняется значительно быстрее.
ВЫБРАТЬ
ТабДок.Заказ,
ВЫБОР
КОГДА (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) > ЗаказыПокупателейОбороты.СуммаУпрПриход
ТОГДА ЗаказыПокупателейОбороты.СуммаУпрПриход + ((ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) - ЗаказыПокупателейОбороты.СуммаУпрПриход)
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаУпрПриход - (ЗаказыПокупателейОбороты.СуммаУпрПриход - (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))))
КОНЕЦ КАК СуммаУпрПриход,
ВЫБОР
КОГДА ЗаказыПокупателейОбороты.НДСПриход = 0
ТОГДА ВЫБОР
КОГДА ЗаказыПокупателейОбороты.СтавкаНДС В (ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.БезНДС), ЗНАЧЕНИЕ(Перечисление.СтавкиНДС.НДС0))
ТОГДА ВЫБОР
КОГДА (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) > ЗаказыПокупателейОбороты.СуммаУпрПриход
ТОГДА ЗаказыПокупателейОбороты.СуммаУпрПриход + ((ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))) - ЗаказыПокупателейОбороты.СуммаУпрПриход)
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаУпрПриход - (ЗаказыПокупателейОбороты.СуммаУпрПриход - (ВЫРАЗИТЬ(ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))))
КОНЕЦ
ИНАЧЕ ЗаказыПокупателейОбороты.СуммаБезНДСПриход
КОНЕЦ
ИНАЧЕ ВЫРАЗИТЬ((ЗаказыПокупателейОбороты.СуммаВзаиморасчетовПриход - ЗаказыПокупателейОбороты.НДСПриход) * ЕСТЬNULL(РегКурсы1.Курс, 1) * ЕСТЬNULL(РегКурсы2.Кратность, 1) / (ЕСТЬNULL(РегКурсы2.Курс, 1) * ЕСТЬNULL(РегКурсы1.Кратность, 1)) КАК ЧИСЛО(15, 2))
КОНЕЦ КАК СуммаБезНДСПриход
ИЗ
ТабДок КАК ТабДок
ЛЕВОЕ СОЕДИНЕНИЕ ЗаказыПокупателейОбороты КАК ЗаказыПокупателейОбороты
ПО ТабДок.Заказ = ЗаказыПокупателейОбороты.ЗаказПокупателя
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК РегКурсы1
ПО ТабДок.ВалютаДокумента = РегКурсы1.Валюта
И (РегКурсы1.Период = ТабДок.ДеньДокумента)
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.КурсыВалют КАК РегКурсы2
ПО (РегКурсы2.Валюта = &ВалютаУпр)
И (РегКурсы2.Период = ТабДок.ДеньДокумента)
В этом заключительном блоке запроса мы используем ЛЕВОЕ СОЕДИНЕНИЕ для связывания уже подготовленных временных таблиц ТабДок и ЗаказыПокупателейОбороты, а также двух экземпляров регистра сведений КурсыВалют. Все сложные вычисления, связанные с валютными курсами и корректировками сумм (которые автор запроса отметил как необходимые из-за особенностей учета), выполняются уже над минимально необходимым объемом данных. Это минимизирует время выполнения запроса и, как показала практика, позволяет отчету формироваться с нормальной скоростью.
Помимо точечной оптимизации запросов, существуют и другие важные аспекты, влияющие на производительность отчетов в 1С. Особенно это актуально для больших и самописных баз данных. Давайте рассмотрим эти рекомендации подробнее.
Для самописных регистров и документов крайне важно убедиться, что все измерения, ресурсы и реквизиты, используемые в условиях отбора, соединениях или для группировки данных, правильно проиндексированы. Недостаточное или некорректное индексирование — одна из наиболее частых причин медленной работы запросов.
ГДЕ или ПО при соединениях.Как мы уже увидели на примере успешного решения, передача параметров отбора (например, по ЗаказПокупателя В (...)) непосредственно в виртуальную таблицу регистра накопления .Обороты() является одним из мощнейших инструментов оптимизации. Платформа 1С и СУБД способны значительно более эффективно обрабатывать запросы, если им заранее известен точный диапазон или набор значений для агрегации.
Мы должны стремиться всегда максимально точно задавать все доступные параметры виртуальных таблиц:
Период (начало и конец периода)РегистраторЗаказПокупателя В (...)).Чем точнее мы укажем эти параметры, тем меньше данных придется "перебирать" и агрегировать, что прямо влияет на скорость выполнения запроса.
В сложных случаях, когда стандартные методы оптимизации не приносят желаемого результата, необходимо провести глубокий анализ плана выполнения запроса (Query Plan). Этот инструмент позволяет нам "увидеть" внутреннюю работу СУБД при выполнении запроса и точно определить "узкие места".
Для больших клиент-серверных баз данных, особенно работающих на SQL Server, регулярное и качественное обслуживание является основой стабильной и высокой производительности. Отсутствие этих процедур может привести к снижению скорости запросов, даже если они написаны оптимально.
Проблема, когда отчеты "виснут" с началом нового года или нового отчетного периода, является весьма типичной. Мы можем выделить несколько причин такого поведения:
ЗаказПокупателя, новые СтавкаНДС), которых не было ранее, или измениться пропорции существующих значений. Это может привести к тому, что ранее эффективные индексы станут менее полезными для СУБД, а старые планы запросов перестанут быть оптимальными.Понимание этих особенностей позволяет нам предвидеть проблемы и заранее планировать меры по оптимизации.
Оптимизация производительности отчетов в 1С – это комплексная задача, требующая как глубокого понимания логики ваших запросов, так и знания особенностей работы платформы и СУБД. Использование временных таблиц для предварительной фильтрации данных, точная передача параметров виртуальным таблицам регистров и регулярное обслуживание базы данных являются ключевыми факторами успеха в борьбе с "тормозящими" отчетами, особенно в больших и самописных информационных базах.
Мы выяснили, что неэффективные запросы, напрямую соединяющие большие объемы данных из виртуальных таблиц, приводят к значительным задержкам. Разбиение запроса на этапы с использованием временных таблиц, а также точечная передача параметров в виртуальные таблицы 1С позволяют существенно сократить объем обрабатываемой информации на каждом шаге. Применяя эти подходы, мы можем значительно улучшить пользовательский опыт, обеспечить быструю и эффективную работу с отчетностью, избегая длительного ожидания формирования критически важных отчетов.