Почему зависает запрос 1С при выборе данных из нескольких таблиц и как это исправить?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

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

Анализ проблемы: что такое «декартово произведение»

Рассмотрим исходную ситуацию. Разработчик пытается получить данные из двух регистров сведений: ФИОФизЛиц (срез последних) и ЛицевыеСчетаРаботниковОрганизации. Запрос выглядит следующим образом:


ВЫБРАТЬ
    ФИОФизЛицСрезПоследних.Фамилия КАК Фамилия,
    ФИОФизЛицСрезПоследних.Имя КАК Имя,
    ФИОФизЛицСрезПоследних.Отчество КАК Отчество,
    ЛицевыеСчетаРаботниковОрганизации.НомерЛицевогоСчета
ИЗ
    РегистрСведений.ФИОФизЛиц.СрезПоследних КАК ФИОФизЛицСрезПоследних,
    РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчетаРаботниковОрганизации

Проанализируем ситуацию. В данном тексте запроса таблицы просто перечислены через запятую в секции ИЗ. В языке SQL (и языке запросов 1С) это означает выполнение операции CROSS JOIN или «перекрестное соединение».

Выясним причину зависания. При таком подходе система пытается соединить каждую строку из первой таблицы с каждой строкой из второй таблицы. Если в справочнике физических лиц у нас 1 000 записей, а в регистре лицевых счетов — 5 000, то результатом запроса станет таблица объемом в 5 000 000 строк (1 000 * 5 000). Если же данных больше, количество строк в результате растет в геометрической прогрессии, что приводит к исчерпанию оперативной памяти и перегрузке дисковой подсистемы сервера (TempDB).

Шаг 1: Правильное использование соединений (JOIN)

Чтобы исправить ошибку, необходимо явно указать системе, по какому полю (ключу) должны сопоставляться записи из разных таблиц. Чаще всего для этой цели используется ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ по общему измерению, например, ФизЛицо.

Посмотрим на пример исправленного кода:


ВЫБРАТЬ
    ФИОФизЛицСрезПоследних.Фамилия КАК Фамилия,
    ФИОФизЛицСрезПоследних.Имя КАК Имя,
    ФИОФизЛицСрезПоследних.Отчество КАК Отчество,
    ЛицевыеСчетаРаботниковОрганизации.НомерЛицевогоСчета КАК ЛицевойСчет
ИЗ
    РегистрСведений.ФИОФизЛиц.СрезПоследних КАК ФИОФизЛицСрезПоследних
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчетаРаботниковОрганизации
        ПО ФИОФизЛицСрезПоследних.ФизЛицо = ЛицевыеСчетаРаботниковОрганизации.ФизЛицо

В этом варианте мы добавили конструкцию ПО, которая ограничивает выборку. Теперь сервер SQL не будет перемножать таблицы, а найдет для каждого физического лица его конкретный лицевой счет.

Шаг 2: Избавление от избыточного разыменования ссылок

Разберем важный нюанс, упомянутый в обсуждении. Часто программисты пишут условие связи так: ПО Таблица1.ФизЛицо.Ссылка = Таблица2.ФизЛицо.Ссылка.

Рассмотрим подробнее, почему это плохо:

  1. Избыточность: Поле ФизЛицо уже само по себе является ссылкой. Добавление .Ссылка не меняет сути данных, но заставляет систему выполнять лишние действия.
  2. Производительность: В некоторых случаях (особенно при работе с составными типами данных) обращение через точку («разыменование») приводит к неявному левому соединению с основной таблицей объекта. Это создает лишнюю нагрузку на СУБД.

Правило: Всегда сравнивайте ссылочные поля напрямую: ПО Таблица1.ФизЛицо = Таблица2.ФизЛицо.

Шаг 3: Оптимизация работы с виртуальными таблицами

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

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

Разберем по шагам, как сделать запрос «профессиональным»:

  1. Использование параметров: Никогда не оставляйте скобки виртуальной таблицы СрезПоследних() пустыми, если вам нужны данные на конкретную дату или по конкретному фильтру. Всегда передавайте &Период.
  2. Временные таблицы: Поместите данные из СрезПоследних во временную таблицу.
  3. Индексация: Проиндексируйте временную таблицу по полю связи.

Посмотрим на пример оптимизированного кода:


// 1. Помещаем данные во временную таблицу
ВЫБРАТЬ
    ФИО.ФизЛицо КАК ФизЛицо,
    ФИО.Фамилия,
    ФИО.Имя,
    ФИО.Отчество
ПОМЕСТИТЬ ВТ_ФИО
ИЗ
    РегистрСведений.ФИОФизЛиц.СрезПоследних(&Период, ) КАК ФИО
ИНДЕКСИРОВАТЬ ПО
    ФизЛицо
;

// 2. Соединяем временную таблицу с физической
ВЫБРАТЬ
    ВТ_ФИО.Фамилия,
    ВТ_ФИО.Имя,
    ВТ_ФИО.Отчество,
    ЛицевыеСчета.НомерЛицевогоСчета
ИЗ
    ВТ_ФИО КАК ВТ_ФИО
        ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчета
        ПО ВТ_ФИО.ФизЛицо = ЛицевыеСчета.ФизЛицо

Подведем итоги

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

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

← На главную