При разработке отчетов или обработок в системе 1С:Предприятие программисты часто сталкиваются с ситуацией, когда запрос, который должен выполняться мгновенно, приводит к полному «зависанию» сеанса или даже всей базы данных. В этой статье мы разберем классический пример такой ошибки, выясним технические причины происходящего и научимся писать оптимальные запросы для связи нескольких таблиц.
Рассмотрим исходную ситуацию. Разработчик пытается получить данные из двух регистров сведений: ФИОФизЛиц (срез последних) и ЛицевыеСчетаРаботниковОрганизации. Запрос выглядит следующим образом:
ВЫБРАТЬ
ФИОФизЛицСрезПоследних.Фамилия КАК Фамилия,
ФИОФизЛицСрезПоследних.Имя КАК Имя,
ФИОФизЛицСрезПоследних.Отчество КАК Отчество,
ЛицевыеСчетаРаботниковОрганизации.НомерЛицевогоСчета
ИЗ
РегистрСведений.ФИОФизЛиц.СрезПоследних КАК ФИОФизЛицСрезПоследних,
РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчетаРаботниковОрганизации
Проанализируем ситуацию. В данном тексте запроса таблицы просто перечислены через запятую в секции ИЗ. В языке SQL (и языке запросов 1С) это означает выполнение операции CROSS JOIN или «перекрестное соединение».
Выясним причину зависания. При таком подходе система пытается соединить каждую строку из первой таблицы с каждой строкой из второй таблицы. Если в справочнике физических лиц у нас 1 000 записей, а в регистре лицевых счетов — 5 000, то результатом запроса станет таблица объемом в 5 000 000 строк (1 000 * 5 000). Если же данных больше, количество строк в результате растет в геометрической прогрессии, что приводит к исчерпанию оперативной памяти и перегрузке дисковой подсистемы сервера (TempDB).
Чтобы исправить ошибку, необходимо явно указать системе, по какому полю (ключу) должны сопоставляться записи из разных таблиц. Чаще всего для этой цели используется ЛЕВОЕ СОЕДИНЕНИЕ или ВНУТРЕННЕЕ СОЕДИНЕНИЕ по общему измерению, например, ФизЛицо.
Посмотрим на пример исправленного кода:
ВЫБРАТЬ
ФИОФизЛицСрезПоследних.Фамилия КАК Фамилия,
ФИОФизЛицСрезПоследних.Имя КАК Имя,
ФИОФизЛицСрезПоследних.Отчество КАК Отчество,
ЛицевыеСчетаРаботниковОрганизации.НомерЛицевогоСчета КАК ЛицевойСчет
ИЗ
РегистрСведений.ФИОФизЛиц.СрезПоследних КАК ФИОФизЛицСрезПоследних
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчетаРаботниковОрганизации
ПО ФИОФизЛицСрезПоследних.ФизЛицо = ЛицевыеСчетаРаботниковОрганизации.ФизЛицо
В этом варианте мы добавили конструкцию ПО, которая ограничивает выборку. Теперь сервер SQL не будет перемножать таблицы, а найдет для каждого физического лица его конкретный лицевой счет.
Разберем важный нюанс, упомянутый в обсуждении. Часто программисты пишут условие связи так: ПО Таблица1.ФизЛицо.Ссылка = Таблица2.ФизЛицо.Ссылка.
Рассмотрим подробнее, почему это плохо:
ФизЛицо уже само по себе является ссылкой. Добавление .Ссылка не меняет сути данных, но заставляет систему выполнять лишние действия.Правило: Всегда сравнивайте ссылочные поля напрямую: ПО Таблица1.ФизЛицо = Таблица2.ФизЛицо.
Хотя запрос с соединением уже будет работать, для высоконагруженных систем этого недостаточно. Проанализируем ситуацию с точки зрения стандартов разработки «1С».
Соединение виртуальных таблиц (таких как СрезПоследних) напрямую с другими физическими таблицами является нерекомендуемым методом. SQL-серверу сложно построить эффективный план запроса для подзапроса, которым является виртуальная таблица.
Разберем по шагам, как сделать запрос «профессиональным»:
СрезПоследних() пустыми, если вам нужны данные на конкретную дату или по конкретному фильтру. Всегда передавайте &Период.СрезПоследних во временную таблицу.Посмотрим на пример оптимизированного кода:
// 1. Помещаем данные во временную таблицу
ВЫБРАТЬ
ФИО.ФизЛицо КАК ФизЛицо,
ФИО.Фамилия,
ФИО.Имя,
ФИО.Отчество
ПОМЕСТИТЬ ВТ_ФИО
ИЗ
РегистрСведений.ФИОФизЛиц.СрезПоследних(&Период, ) КАК ФИО
ИНДЕКСИРОВАТЬ ПО
ФизЛицо
;
// 2. Соединяем временную таблицу с физической
ВЫБРАТЬ
ВТ_ФИО.Фамилия,
ВТ_ФИО.Имя,
ВТ_ФИО.Отчество,
ЛицевыеСчета.НомерЛицевогоСчета
ИЗ
ВТ_ФИО КАК ВТ_ФИО
ЛЕВОЕ СОЕДИНЕНИЕ РегистрСведений.ЛицевыеСчетаРаботниковОрганизации КАК ЛицевыеСчета
ПО ВТ_ФИО.ФизЛицо = ЛицевыеСчета.ФизЛицо
Зависание запроса — это почти всегда сигнал о том, что система выполняет лишнюю работу. Чтобы ваши запросы работали быстро, следуйте этим рекомендациям:
ПО (или секции ГДЕ для старых стандартов) при выборе данных из нескольких источников..Ссылка к полям, которые уже являются ссылочными.МенеджерВременныхТаблиц и индексируйте поля связи.Используя эти методы, вы обеспечите стабильную работу системы и комфортную работу пользователей без досадных зависаний программы.