Проблемы производительности: Оптимизация запросов с оператором «В» для составных типов в 1С и СУБД Postgres

10.11.25

База данных - HighLoad оптимизация

Использование оператора «В» для полей или данных составного типа (например, Регистратор) может приводить к неочевидным проблемам.

В современных высоконагруженных ERP-системах на платформе 1С, работающих с базами данных объемом от 1 ТБ и обслуживающих сотни и тысячи одновременно работающих пользователей, проблемы производительности запросов иногда могут приводить к серьезным последствиям для оперативной деятельности бизнеса. К одним из самых сложных случаев относятся проблемы, которые возникают время от времени и из зависят о неопределенных факторов. 

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

  • Полную методику диагностики проблем производительности в связке 1С и PostgreSQL
  • Глубокий анализ архитектурных особенностей при работе с полями с составными типами в запросах на разных уровнях (1C и СУБД PostgreSQL)
  • Подробное сравнение альтернативных подходов к оптимизации
  • Практические рекомендации по предотвращению подобных ситуаций на этапе разработки

 

1. Детальное описание проблемы


1.1. Бизнес-контекст и симптомы


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

  • 1,400+ одновременных пользователей

Проблема проявилась в модуле складского учета при:

  • Проведении приходных ордеров
  • Формировании расходных накладных
  • Оформлении перемещений

Пользователи получали ошибки:

«Конфликт блокировок при выполнении транзакции:
        Превышено максимальное время ожидания предоставления блокировки»

1.2. Техническое окружение

Программное обеспечение:

  • PostgreSQL 15.3 с настройками для 1С
  • Платформа 1С:Предприятие 8.3.23.1688

Характеристики базы:

  • 1С:ERP 2.5.12.112
  • Общий размер: 1.8+ ТБ

 

2. Подтверждение и диагностика проблемы блокировок

 

2.1. Подтверждение и первичный анализ проблемы

 

В рамках сопровождения высоконагруженной ERP-системы нами реализована комплексная система мониторинга, обеспечивающая непрерывный контроль ключевых показателей производительности. Для получения таких метрик мы используем анализ событий блокировок из технологического журнала, который в нашей системе мониторинга отображается под названием «Блокировки - 1C & POSTGRES».

Информация о возникновении проблем была подтверждена. Для детального изучения мы проанализировали указанный выше замер, отфильтровав события по пользователю «Пользователь-8».

 

Рис.1. Список событий замеров для замера «Блокировки - 1C & POSTGRES»

 

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

Для поиска корневой причины мы фокусируемся на колонке WaitConnections. Её значение показывает, какие именно соединения к СУБД не позволяет завершить операцию текущему пользователю («Пользователь-8»).

Небольшое резюме по ситуации на рисунке:

  • В строке, соответствующей времени 01.04.2024 11:52:31, значение WaitConnections равно списку блокировок 23929, 18835, 239, 743. Получается что в этот момент существует небольшая очередь к нужной таблице.
  • Важно отметить, что это не единичный случай. Анализ лога показывает, что на протяжении нескольких минут различные сессии ожидают освобождения ресурсов. Возьмем номер соединения 23929 для дальнейшей идентификации «виновника» (blocker) и основную цель для дальнейшей диагностики.

 

2.2. Поиск виновника

 

Чтобы определить виновника блокировки, воспользуемся полученной ранее информацией и выясним, какой сеанс и какой пользователь её вызывают.

Для этого мы используем замер «Данные 1С RAS». Этот замер хранит информацию о соединениях и сеансах пользователей и сохраняется с интервалом в 60 секунд.

 

Рис. Форма обработки просмотра событий соединений RAS 1C

 

Далее перейдём к информации на вкладке «Сеансы».

 

Рис. Форма обработки просмотра сеансов RAS 1C

 

После перехода на вкладку «Сеансы» установим отбор по номеру найденного проблемного сеанса и обратим внимание на ключевые показатели.

Нас интересует имя пользователя — в данном случае это Пользователь-171. Также стоит обратить внимание на значение в колонке «Захвачено СУБД». Этот параметр показывает время, которое пользователь занимает на получение данных из базы данных, то есть время выполнения запроса. В нашем случае оно составляет около 18 секунд.

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

 

3. Определение причины проблемы

 

3.1 Поиск контекста

 

Переходим к замеру с названием «Длительные запросы - 1C & POSTGRES». Применим отбор по пользователю и номеру сеанса, чтобы найти нужные события.

 

Рис. Список событий замеров для замера «Длительные запросы - 1C & POSTGRES»

 

Действительно, мы видим, что в это время существовал некий длительный запрос, который попал в технологический журнал. Отсюда мы можем получить ценную информацию: контекст выполнения (место в коде, откуда был вызван запрос) и текст самого SQL-запроса.

 

Рис. Контекст выполнения запроса

 

Как мы видим, проблемное событие возникает в модуле менеджера регистра накопления «Запасы и потребности». Теперь, когда мы определили источник проблемного запроса, перейдём к его анализу.

Откроем Конфигуратор, найдём соответствующий исходный код модуля и идентифицируем запрос-виновник.

 
  Код 1С проблемного участка

 

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

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура,
	Товары.Характеристика КАК Характеристика,
	Товары.Склад КАК Склад,
	Товары.Назначение КАК Назначение,
	Товары.Заказ КАК Заказ,
	Товары.ДатаСобытия КАК ДатаСобытия
ИЗ
	ДвиженияПриЗаписи КАК Товары

ОБЪЕДИНИТЬ

ВЫБРАТЬ РАЗЛИЧНЫЕ
	Движения.Номенклатура,
	Движения.Характеристика,
	Движения.Склад,
	Движения.Назначение,
	Движения.Заказ,
	Движения.ДатаСобытия
ИЗ
	РегистрНакопления.ЗапасыИПотребности КАК Движения
ГДЕ
	Движения.Регистратор В(&Регистраторы)
	И Движения.Активность

 

 

Перед нами — пакет запросов. Чтобы понять какой из двух запросов пакета виновен (в принципе и так можно догадаться), обратим внимание на текст SQL-запроса. И теперь сделаем вывод, что это второй запрос в этом пакете.

 

Рис. Текст SQL запроса

 

Но почему он проблемный? На первый взгляд, ничего криминального здесь нет (оператор РАЗЛИЧНЫЕ конечно же не очень хорошо, но у нас присутствует "сильный" отбор). В запросе используется фильтр по регистратору, для которого всегда существует индекс. Давайте посмотрим план запроса, чтобы выяснить в чем проблема.

 

3.2. Анализ плана запроса

 

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

https://explain.tensor.ru/archive/explain/2a4e76547ce6fb66ed59db53312f7159:0:2024-04-14#visio

Прежде чем двигаться дальше, кратко проясним соответствие операторов плана запроса исходному коду на языке 1С.

 

Рис. Соответствие операторов плану запросов

 

  • Верхняя часть плана (красный прямоугольник): Два оператора относятся к первой выборке, где данные берутся из временной таблицы.
  • Оператор APPEND в связке с AGGREGATE является прямым аналогом команды «Объединить» в 1С.
  • Нижняя часть плана (синий прямоугольник): Отвечает за выборку данных из регистра накопления. 

Возникает вопрос: почему в нижней части мы видим еще один оператор AGGREGATE (объединение)? Мы же выбираем данные из одного источника - из регистра накопления.

На тексте запроса на языке запросов 1С этого мы не увидим. Тогда посмотрим на SQL-текст запроса. И мы там увидим кое что необычное (выделено на рисунке красным прямоугольником).

 

Рис. Наличие оператора ИЛИ в SQL-тексте запроса

 

Это объединение появляется из-за наличия в SQL запросе условия с оператором ИЛИ. И именно так планировщик СУБД оптимизирует выполнение такого условия. Он формирует выборку из одной таблицы отдельно по каждому из условий вокруг ИЛИ. Согласитесь что планировщик Postgres ведет себя достаточно умно в этом случае? 

Откуда взялся оператор ИЛИ, мы разберем в следующей части расследования, а сейчас сосредоточимся на анализе самой проблемы.

 

Рис. План проблемного запроса — свойства проблемного оператора получения данных

 

На плане запроса обращаем внимание на самый большой красный круг. Такое явное выделение указывает на наличие проблемы при выполнении запроса. Эта часть плана запроса находится в области выборки данных из регистра накопления. Ее мы и будем рассматривать в дальнейшем.

Если мы наведем мышку на этот оператор, то во всплывающем окне появится расшифровка. Давайте посмотрим на нее подробнее. И мы видим, что данные выбираются через индекс регистра накопления «Запасы и потребности», однако эта операция выполняется очень долго. И это неудивительно: в процессе отбирается всего 4 записи, но для этого впустую перебирается почти 2.5 млн строк. На это прямо указывает значение Rows Removed by Filter (RRBF) — количество строк, отброшенных фильтром после сканирования.

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

  1. Неселективный отбор по индексу: Условие в индексе включает общий реквизит и тип.
    • Все записи имеют единственное значение общего реквизита — 0. Это поле практически бесполезно для отбора.
    • Различных типов не так много, а записей по ним — огромное количество. Без отбора по значению ссылки индекс захватывает слишком широкий диапазон данных.
  2. Ключевое условие — в фильтре: Отбор по самому значению ссылки находится не в условиях доступа к индексу где должно находиться, а в фильтре (Filter). Это означает, что сначала будут отсканированы все записи, подходящие под нестрогий индекс (общий реквизит=0 и тип=X), а уже потом к этим миллионам записей будет применено условие по ссылке.
  3. Избыточное условие: В фильтре присутствует условие OR для типа, которое уже частично учтено при поиске по индексу, но это не критично.

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

Вернёмся к плану запроса. Теперь посмотрим, как выполняется получение данных из второго объединения.

 

Рис. План выполнения запроса для второго объединения данных

 

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

Мы можем предположить, что выбор данных из источника #6 должен был быть только по одному типу, а из второго источника #9 только по другому типу. В действительности же мы видим, что в условие получения данных (проблемный) кружок планировщик зачем-то добавил отбор и по второму типу. Это явная его ошибка, которая и приводит к проблемам.

 

3.3 Детальный разбор проблемы


Мы поняли что проблема вызвана наличием оператора ИЛИ. Теперь давайте разберемся, откуда в SQL-запросе взялось условие с этим оператором, ведь в исходном запросе на 1С его не было.

Вернемся к тексту SQL-запроса и внимательно изучим условие отбора в секции WHERE.

 

Рис. Фрагмент SQL-запроса с условием в секции WHERE

 

Мы видим что оператор ИЛИ делит условие на две похожие части. В первой ставится отбор по некоторому типу ссылки и условие на вхождение набора ссылок в поле ссылка. Вторая часть - это отбор по другому типу ссылок и их значениям. Почему же получилось такое разделение? 

Синтаксис оператора «В» в 1С и SQL может быть представлен в двух основных вариантах:

  • Проверка вхождения в набор значений: Выражение В (Значение1, Значение2, ...)
  • Проверка вхождения в результат подзапроса: Выражение В (Выбрать из подзапроса)

Теперь ключевой момент: посмотрим, как представлено составное поле «Регистратор» на уровне СУБД. Его структура показана на рисунке ниже.

 

Рис. Схема представления поля "Регистратор" в 1С и СУБД

 

В то время как на уровне 1С это одна колонка, на уровне СУБД она физически разбивается на два поля:

  • RecorderTRef — хранит идентификатор типа документа (ссылку на метаданные).
  • RecorderRRef — хранит непосредственно уникальный идентификатор (GUID) самого документа-регистратора.

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

 

Рис. Схема таблицы регистра накопления на уровнях 1С и СУБД (не показаны поля измерений, ресурсов и реквизитов)

 

Первый вариант использования оператора «В» позволяет выполнять проверку только по одномерному массиву, и в исходном 1С запросе это именно так и выглядит.

Однако когда мы переходим на уровень СУБД, ситуация меняется. Наш одномерный массив ссылок на уровне базы данных становится двумерным, так как каждая ссылка представлена двумя полями: RecorderTRef (тип) и RecorderRRef (идентификатор). Невозможно напрямую записать условие В для такой составной структуры.

Чтобы сохранить ту же логику, платформа 1С вынуждена преобразовать исходное условие. Вместо простого оператора В генерируется эквивалентная конструкция, представляющая собой набор условий, объединенных через «ИЛИ»:


Вот почему появляется оператор ИЛИ (OR). Такое преобразование приводит к созданию громоздкого условия ИЛИ. Планировщику запросов СУБД зачастую сложно эффективно построить план для таких конструкций, особенно когда список значений велик, что мы и наблюдаем в виде медленного выполнения.

Важное замечание!

Может возникнуть предположение, что условие по типу (RecorderTRef) можно было бы не использовать, сравнивая только по GUID (RecorderRRef). Однако это привело бы к некорректной работе запроса.

Почему это опасно: Если в разных типах объектов окажутся одинаковые GUID, запрос вернет лишние или неверные записи. Такая ситуация — не гипотетическая. Она регулярно возникает, например, при выполнении обменов данными.

Реальный пример из практики: При выгрузке документа «Реализация товаров и услуг» из конфигурации ERP в «Бухгалтерию» один исходный документ может разделяться на два в системе-приемнике (например, при использовании механизма «товаров в пути»). Самым удобным техническим решением в этом случае часто является сохранение одного и того же GUID для обоих создаваемых документов.

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

 

Варианты решения


Вариант 1. Замена условия «ИЛИ» на оператор «Объединение»


Одно из самых очевидных решений — это изменить исходный запрос, заменив условие ИЛИ на оператор ОБЪЕДИНЕНИЕ.

Идея заключается в том, чтобы разбить один сложный запрос с условием ИЛИ на несколько отдельных запросов, результаты которых затем объединяются.

В нашем примере использовалось два типа ссылок. Давайте разберем его реализацию.

 
 Код 1С - оптимизация вариант 1

 

ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура,
	Товары.Характеристика КАК Характеристика,
	Товары.Ссылка.Склад КАК Склад,
	Товары.Назначение КАК Назначение,
	Товары.ДокументОтгрузки КАК Заказ,
	Товары.Ссылка.Дата КАК ДатаСобытия
ПОМЕСТИТЬ ДвиженияПриЗаписи
ИЗ
	Документ.ПриходныйОрдерНаТовары.Товары КАК Товары
ГДЕ
	Товары.Ссылка = &Ссылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура,
	Товары.Характеристика КАК Характеристика,
	Товары.Склад КАК Склад,
	Товары.Назначение КАК Назначение,
	Товары.Заказ КАК Заказ,
	Товары.ДатаСобытия КАК ДатаСобытия
ИЗ
	ДвиженияПриЗаписи КАК Товары

ОБЪЕДИНИТЬ

ВЫБРАТЬ РАЗЛИЧНЫЕ
	Движения.Номенклатура,
	Движения.Характеристика,
	Движения.Склад,
	Движения.Назначение,
	Движения.Заказ,
	Движения.ДатаСобытия
ИЗ
	РегистрНакопления.ЗапасыИПотребности КАК Движения
ГДЕ
	Движения.Регистратор В(&РегистраторыТип1)
	И Движения.Активность

ОБЪЕДИНИТЬ

ВЫБРАТЬ РАЗЛИЧНЫЕ
	Движения.Номенклатура,
	Движения.Характеристика,
	Движения.Склад,
	Движения.Назначение,
	Движения.Заказ,
	Движения.ДатаСобытия
ИЗ
	РегистрНакопления.ЗапасыИПотребности КАК Движения
ГДЕ
	Движения.Регистратор В(&РегистраторыТип2)
	И Движения.Активность

 

 

После выполнения запроса, использующего UNION, мы получаем следующий план:

 

Рис. План запроса - Вариант 1

 

Запрос стал выполняться очень быстро, что подтверждает эффективность данной оптимизации — время выполнения составляет менее 1 мс.

https://explain.tensor.ru/archive/explain/4575fc70a5e9c43f34e5fe1f5ca966b4:0:2024-04-03#visio

Однако у этого метода есть существенные недостатки:

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

В результате такое решение становится громоздким и сложным для поддержки, особенно если список типов может меняться.

 

Вариант 2. Использование оператора «В» с подзапросом

 

Исправим ситуацию, воспользовавшись вторым вариантом синтаксиса оператора «В» — с подзапросом.

Перепишем исходный запрос следующим образом:

 
 Код 1С - оптимизация вариант 2

 

ВЫБРАТЬ
	Т.Регистратор КАК Регистратор
ПОМЕСТИТЬ ВтРегистраторы
ИЗ
	&ВтТЗ КАК Т
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура,
	Товары.Характеристика КАК Характеристика,
	Товары.Ссылка.Склад КАК Склад,
	Товары.Назначение КАК Назначение,
	Товары.ДокументОтгрузки КАК Заказ,
	Товары.Ссылка.Дата КАК ДатаСобытия
ПОМЕСТИТЬ ДвиженияПриЗаписи
ИЗ
	Документ.ПриходныйОрдерНаТовары.Товары КАК Товары
ГДЕ
	Товары.Ссылка = &ССылка
;

////////////////////////////////////////////////////////////////////////////////
ВЫБРАТЬ РАЗЛИЧНЫЕ
	Товары.Номенклатура КАК Номенклатура,
	Товары.Характеристика КАК Характеристика,
	Товары.Склад КАК Склад,
	Товары.Назначение КАК Назначение,
	Товары.Заказ КАК Заказ,
	Товары.ДатаСобытия КАК ДатаСобытия
ИЗ
	ДвиженияПриЗаписи КАК Товары

ОБЪЕДИНИТЬ

ВЫБРАТЬ РАЗЛИЧНЫЕ
	Движения.Номенклатура,
	Движения.Характеристика,
	Движения.Склад,
	Движения.Назначение,
	Движения.Заказ,
	Движения.ДатаСобытия
ИЗ
	РегистрНакопления.ЗапасыИПотребности КАК Движения
ГДЕ
	Движения.Регистратор В
			(ВЫБРАТЬ
				Вт.Регистратор
			ИЗ
				ВтРегистраторы КАК Вт)
	И Движения.Активность

 

 

При использовании этого варианта запрос также выполняется очень быстро. Время выполнения составляет менее 1 мс.

 

Рис. План запроса - вариант 2

 

https://explain.tensor.ru/archive/explain/b3c210c856f6961269b6519860fe4108:0:2024-04-03#visio

Оценка решения:

Преимущество: В отличие от первого варианта, здесь отсутствует громоздкая конструкция запроса с множеством UNION. Текст запроса остается простым и читаемым.

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

 

Реализация исправления


Для устранения проблемы был выбран Вариант 2, так как он обеспечивает высокую производительность и технически реализуется проще, чем разбиение запроса на UNION.

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

План действий:

  1. В Конфигураторе находим объект «РегистрНакопления.ЗапасыИПотребности».
  2. В его модуле менеджера находим функцию УстановитьБлокировкиЗаказов().
  3. С помощью механизма расширений добавляем эту функцию в список объектов для контроля и изменения (Вариант контроля: «Вызов исключения», Вариант изменения: «Замена»).
  4. В коде функции находим участок, где формируется параметр «Регистраторы» (массив значений, передаваемый в запрос).
  5. Ключевое изменение: Заменяем передачу простого массива на передачу таблицы значений, которая будет использоваться в подзапросе.
 
 Код исправления (первая часть)

 

Функция УстановитьБлокировкиЗаказовИТоваров(НаборЗаписей, Ссылка, ЭтоОбновлениеИБ)
	
//...
//...

	БлокировкаДанных.Заблокировать();
	
	#Удаление
	Регистраторы = Регистраторы.Выгрузить().ВыгрузитьКолонку("Регистратор");
	Регистраторы.Добавить(Ссылка);
	#КонецУдаления
	#Вставка     
	Регистраторы = Регистраторы.Выгрузить();
	стр_н = Регистраторы.Добавить();
	стр_н.Регистратор = Ссылка;
	#КонецВставки

	Запрос.УстановитьПараметр("Регистраторы", Регистраторы);
	Запрос.УстановитьПараметр("ТаблицаТовары", НаборЗаписей.Выгрузить());
	
	//...
//...

 

 

Замечание!

В коде выше достаточно плохо выглядит решение о переиспользовании переменной «Регистраторы» со сменой типа. Более правильно выглядит введение новой переменной.

И нам осталось еще в самом запросе добавить создание временной таблицы и изменить условие на новое (блоки удаления и вставки):

 
 Код исправления (вторая часть)

 

//....

	Запрос.Текст =               
	#Вставка     
	"ВЫБРАТЬ                                 
	|	ТЗ.Регистратор КАК Регистратор
	|ПОМЕСТИТЬ ВтРегистраторы
	|ИЗ
	|   &Регистраторы как ТЗ
	|;
	|"+
	#КонецВставки
	"ВЫБРАТЬ РАЗЛИЧНЫЕ
	|	Товары.Номенклатура КАК Номенклатура,
	|	Товары.Характеристика КАК Характеристика,
	|	Товары.Склад КАК Склад,
	|	Товары.Назначение КАК Назначение,
//....

	//....
	|	Движения.ДатаСобытия КАК ДатаСобытия
	|ИЗ
	|	РегистрНакопления.ЗапасыИПотребности КАК Движения
	|ГДЕ                                    
	#Удаление
	|	Движения.Регистратор В (&Регистраторы)
	#КонецУдаления
	#Вставка                                  
	|	Движения.Регистратор В (ВЫБРАТЬ Вт.Регистратор ИЗ ВтРегистраторы КАК Вт)
	#КонецВставки
	|		И Движения.Активность";

	Товары = Запрос.Выполнить();
	//....

	//....
	Возврат Результат;
КонецФункции

 

 

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

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

 

Заключение


Давайте оценим эффективность проведённой оптимизации. Разделив исходное время выполнения (10 000 мс) на новое (0.3 мс), мы получаем впечатляющий результат:

10 000 мс / 0.3 мс ≈ 33 333 раза.

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

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

Итоги проведенной работы:

  • Проблема решена: Блокировки устранены. Запрос выполняется практически мгновенно, что полностью исключило его негативное влияние на других пользователей.
  • Освоен метод диагностики: Мы рассмотрели практический метод оперативного поиска и анализа причин блокировок в системе 1С.
  • Выявлена ключевая особенность платформы: Мы познакомились с тем, как платформа 1С транслирует оператор «В» для составных типов на уровень SQL, преобразуя его в условие ИЛИ, которое в ряде случаев оказывается крайне неоптимальным для планировщика СУБД. Понимание этого механизма позволяет заранее проектировать запросы, избегая подобных проблем. 

 

 
 видео youtube

 

Вступайте в нашу телеграмм-группу Инфостарт

См. также

HighLoad оптимизация Программист 1С v8.3 1C:ERP Бесплатно (free)

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

18.02.2025    7480    ivanov660    39    

61

HighLoad оптимизация Технологический журнал Системный администратор Программист Бесплатно (free)

Обсудим поиск и разбор причин длительных серверных вызовов CALL, SCALL.

24.06.2024    9931    ivanov660    13    

62

HighLoad оптимизация Программист 1С v8.3 Бесплатно (free)

Метод очень медленно работает, когда параметр приемник содержит намного меньше свойств, чем источник.

06.06.2024    15741    Evg-Lylyk    73    

45

HighLoad оптимизация Программист 1С v8.3 1C:Бухгалтерия Бесплатно (free)

Анализ простого плана запроса. Оптимизация нагрузки на ЦП сервера СУБД используя типовые индексы.

13.03.2024    7710    spyke    29    

53

HighLoad оптимизация Программист 1С v8.3 Бесплатно (free)

Оказывается, в типовых конфигурациях 1С есть, что улучшить!

13.03.2024    10966    vasilev2015    22    

46

HighLoad оптимизация Инструменты администратора БД Системный администратор Программист 1С v8.3 Абонемент ($m)

Обработка для простого и удобного анализа настроек, нагрузки и проблем с SQL сервером с упором на использование оного для 1С. Анализ текущих запросов на sql, ожиданий, конвертация запроса в 1С и рекомендации, где может тормозить.

5 стартмани

15.02.2024    18552    336    ZAOSTG    100    

124
Комментарии
Подписаться на ответы Инфостарт бот Сортировка: Древо развёрнутое
Свернуть все
1. paulwist 10.11.25 10:10 Сейчас в теме
1. Статья однозначно полезная (+ 100500 очков в карму)


2.
В пиковые периоды система испытывала нагрузки до:

1,400+ одновременных пользователей


Вопрос: само по себе кол-во юзеров мало о чём говорит, если осталась статистика, то какова нагрузка на БД в метрике транзакций DML/сек ??
starik-2005; +1 Ответить
3. ivanov660 4845 10.11.25 11:01 Сейчас в теме
(1) Такую метрику не замеряли, могу сказать что общее количество одномоментно запрашивающих что-то пользователей из базы в среднем в интервалы максимальной нагрузки в районе 100 пользователей.
2. sergey82vladik 6 10.11.25 10:16 Сейчас в теме
Не могу понять, при использовании вложенного запроса результат возвращает и ссылку и тип ссылки? Почему в этом случае планировщик не используется отбор по типу ссылки
4. ivanov660 4845 10.11.25 11:04 Сейчас в теме
(2) Так судя по всему работает планировщик. В данном примере планировщик формировал "плохой" план, в других случаях план был лучше.
5. modelist 10.11.25 12:27 Сейчас в теме
А на сколько изменится производительность при тех же условиях, если условие (ГДЕ) в исправленном запросе заменить на ВНУТРЕННЕЕ соединение к таблице ВТ и туда же добавить условие по признаку активности записей регистра? Просто для понимания: быстрее, медленнее, также...
7. gzharkoj 573 10.11.25 13:13 Сейчас в теме
(5) Судя по плану одинаково отработает - так же создается временная таблица и соединяется.
triviumfan; +1 Ответить
8. ivanov660 4845 10.11.25 13:20 Сейчас в теме
(5)Да, скорее всего отработает также, только запись будет более громоздкая.
Только обратите внимание, что в запросе стоят РАЗЛИЧНЫЕ. Если же не будет этого ключевого слова и будьте осторожнее, иначе можете словить дубли строк.
6. baracuda 2 10.11.25 12:50 Сейчас в теме
Очень полезная и интересная статья.
Получил удовольствие от прочтения.
ivanov660; PowerBoy; +2 Ответить
9. vadim1980 125 10.11.25 16:20 Сейчас в теме
Нужно писать на v8@1c.ru, т.к. в данном случае платформа не учитывает, что в типовом PosrgreSQL оптимизатор по факту отсутствует и не может составлять оптимальный план даже на такие простые запросы
Интересно было бы посмотреть, какой план построит PosrgresPro Enterprise
12. gzharkoj 573 10.11.25 19:15 Сейчас в теме
(9) Дело не в оптимизаторе (всегда будет ограничение на составление "оптимального" плана запроса, а описанный маленький запрос может являться частью одного огромного) - платформа так преобразует запрос, на самом деле это давняя проблема, на вскидку вот статья от 13 года, где это описывают (https://infostart.ru/1c/articles/184361/). Похоже, что специалисты в 1с не видят общее решение этой проблемы на все случаи жизни, но с вами соглашусь, писать надо, возможно, хоть сделают некое решение, которое закроет часть вариантов.
triviumfan; +1 Ответить
16. triviumfan 102 10.11.25 21:10 Сейчас в теме
(12) соглашусь, я тоже давно натыкался на это, проблема известна уж 100 лет назад)
Повторенье - мать учения (с).
10. RocKeR_13 1457 10.11.25 17:03 Сейчас в теме
Реальный пример из практики: При выгрузке документа «Реализация товаров и услуг» из конфигурации ERP в «Бухгалтерию» один исходный документ может разделяться на два в системе-приемнике (например, при использовании механизма «товаров в пути»). Самым удобным техническим решением в этом случае часто является сохранение одного и того же GUID для обоих создаваемых документов.

Все-таки в типовых обменах GUID не переносится, а вместо этого используются служебные регистры сведений ПубличныеИдентификаторыСинхронизируемыхОбъектов (для EnterpriseData) и СоответствияОбъектовИнформационныхБаз (для обменов по правилам). Но в целом согласен: условий для того, чтобы GUID был бы уникальным в пределах всей ИБ, нет; а это означает мизерную, но ненулевую вероятность дублей GUID для разных типов объектов.
11. triviumfan 102 10.11.25 18:10 Сейчас в теме
Плюс поставил, люблю таки статьи, да и оформление зачет, но жаль, что ничего нового не узнал.
15. triviumfan 102 10.11.25 21:06 Сейчас в теме
(11) Кстати, сейчас попытался воспроизвести на ms sql на 3млн записей - не получилось (отбор 5 регистраторов разных типов), отработало за 10мс. А в тестовой базе на pg у меня только 400к записей в этой таблице - там 7мс. Планы даже не смотрел, т.к. с такой производительностью смысла нет. "Плохой" запрос и исправленный отработали одинаково.
PS: 8.3.27.1786, ms sql (16.0), pg 17.2 (с сайта 1с).
PSS: Может на 27й платформе пофиксили?)
13. bulpi 217 10.11.25 19:19 Сейчас в теме
Поставил плюс, но вообще-то проблемность запроса видна сразу. И дело не в каких то тонкостях, а в примитивном использовании реальной таблицы регистра накопления вместо виртуальной таблицы оборотов.
14. triviumfan 102 10.11.25 20:35 Сейчас в теме
(13) А можно пояснение для тех кто в танке?) Причём тут вообще виртуальная таблица?!
17. bulpi 217 10.11.25 23:49 Сейчас в теме
(14) Нужно было использовать таблицу РегистрНакопления.ЗапасыИПотребности. Обороты(), а отбор по регистраторам внести в условие запроса (внутрь скобок, а не в оператор ГДЕ)
18. triviumfan 102 11.11.25 00:36 Сейчас в теме
(17) и какой в ней смысл, если таблица итогов не будет использоваться, да и накопления тут не нужны. Да вообще он будет интерпретирован как такой же только еще с вложенным запросом. Шило на мыло.
22. bulpi 217 11.11.25 11:14 Сейчас в теме
(18) Таблица , из которой выборка, будет намного меньше. 1с во всех пособиях рекомендует так делать
24. ivanov660 4845 11.11.25 11:19 Сейчас в теме
(22) "Запасы И Потребности" регистр накопления с видом остатки, поэтому виртуальная таблица Обороты() не имеет таблицы итогов для оборотов. Следовательно будет создан вложенный запрос (для формирования виртуальной таблицы Обороты), который будет использовать физическую таблицу движений. Отсюда следует, что такой подход эффективнее не будет. Как-то так)
29. bulpi 217 11.11.25 15:05 Сейчас в теме
(24) Не поверил... проверил на своей базе... Вы правы. Но почему тогда 1с во всех пособиях рекомендует так делать?
Хотя общая таблица оборотов без периодичности по регистратору формируется почему-то намного быстрее, чем общая таблица регистра.
30. ivanov660 4845 11.11.25 15:35 Сейчас в теме
(29) "Следите за руками" - я бы сказал так: рекомендации 1С они "немного" размытые и там буквально говориться делайте правильно - будет все хорошо.
Касательно добавляйте условия внутрь виртуальной таблицы - это правильно, т.к. если мы срежем фильтром количество записей внутри вложенного запроса, то это будет лучше чем выбрать все записи из вложенного запроса, а потом обрезать их. Особенно если есть соединения с виртуальной таблицей.
27. triviumfan 102 11.11.25 12:29 Сейчас в теме
(22) Я извиняюсь, но вы вообще не понимаете, что говорите.
1. Таблица будет та же самая - основная таблица регистра накопления, итоги не будут задействованы из-за периодичности регистратора - добавится ещё вложенный запрос, что только усложнит план.
2. Да и вообще невозможно обратиться к регистратору в условии виртуальной таблицы Обороты()
28. bulpi 217 11.11.25 15:02 Сейчас в теме
20. ivanov660 4845 11.11.25 09:50 Сейчас в теме
(17) Преобразование конструкции В () 1С запроса в SQL формат от этого с большой вероятностью не поменяется. К тому же учитывая что это виртуальная таблица, то сам запрос будет сложнее и соответственно план запроса. Поэтому вероятность формирования "плохого" плана увеличится.
23. bulpi 217 11.11.25 11:15 Сейчас в теме
(20) Возможно, пробовать надо. Я просто увидел потенциально слабое место, причем сделал это за 5 секунд и без анализа технологического журнала.
25. ivanov660 4845 11.11.25 11:22 Сейчас в теме
(23) Снимаю шляпу) Мне потребовалось не менее нескольких часов чтобы разобраться в проблеме.
19. muskul 11.11.25 01:36 Сейчас в теме
Та самая история про 1 доллар за удар молотком и 999 за то что бы узнать где ударить
ivanov660; gzharkoj; +2 Ответить
21. shiaju 26 11.11.25 09:50 Сейчас в теме
Статья хорошая, но переменные с именем типа "стр_н" - это ужасно
26. ivanov660 4845 11.11.25 11:28 Сейчас в теме
(21) Предложите лучший вариант для названия одноразовой временной переменной (стр_н - кратко "строка новая"). Запишу в копилку.
starik-2005; +1 Ответить
32. starik-2005 3198 11.11.25 16:30 Сейчас в теме
(26) Я бы просто "нс" назвал. Но критиков это не успокоило бы)))
ivanov660; +1 Ответить
31. starik-2005 3198 11.11.25 16:25 Сейчас в теме
Интересно тут у вас. Друг-товарищ-брат2 на днях жаловался, что у заказчика 8380 платинум (судя по гикбенчу - мой проданный 1600-й райзен быстрее) с овер много памяти, на нем астра, на ней энтерпрайз от постгреса, на нем 8 часов выполняется запрос с тремя левыми соединениями со срезом последних. Да, такое себе. 175к в справочнике, 175к в регистре, 8к в другом регистре и следы чего-то в третьем. Настройки с пгбенча при установке подтянулись, 2к соединений прописано. Динамический список со статусами и чем-то там еще - висит. Переписали на ВТ - не помогает. В итоге поставили убунту, развернули там постгрес - все летает. Снесли ынтерпрайз и поставили обычный от 1С - снова летает. Ну прям мистика.
Для отправки сообщения требуется регистрация/авторизация