При разработке на платформе 1С:Предприятие 8.3 программисты иногда сталкиваются с крайне неприятной проблемой: при выполнении вполне стандартного запроса происходит аварийное завершение работы платформы (крэш рабочего процесса или клиентского приложения). Особенно часто это происходит при попытке оптимизировать выборку из критериев отбора с помощью конструкции ПЕРВЫЕ 1. В этой статье мы подробно разберем, почему возникает эта ошибка, как устроены внутренние механизмы СУБД в этот момент, и рассмотрим несколько надежных способов обхода проблемы.
Проанализируем типичную ситуацию. В привилегированном модуле выполняется простая функция, задача которой — быстро проверить, есть ли в базе данных хотя бы один документ, связанный с исходным объектом. Для этого используется стандартный критерий отбора КритерийОтбора.СвязанныеДокументы.
Разработчик пишет следующий код:
Функция КритерииОтбора__ЕстьСвязанныеДокументы(СсылкаНаОбъект) Экспорт
Результат = Ложь;
Если НЕ ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
Возврат Результат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("СсылкаНаОбъект", СсылкаНаОбъект);
Запрос.Текст =
"ВЫБРАТЬ
| ПЕРВЫЕ 1 // Эта строка вызывает падение платформы!
| Т.Ссылка
|ИЗ
| КритерийОтбора.СвязанныеДокументы(&СсылкаНаОбъект) КАК Т
|ГДЕ
| НЕ Т.Ссылка.ПометкаУдаления";
Результат = НЕ Запрос.Выполнить().Пустой();
Возврат Результат;
КонецФункции
Если закомментировать строку ПЕРВЫЕ 1, запрос отрабатывает штатно и без ошибок. Но стоит вернуть это ограничение выборки, как платформа мгновенно аварийно завершает работу с ошибкой СУБД или операционной системы.
Чтобы понять, почему добавление, казалось бы, оптимизирующей инструкции ломает систему, нам нужно разобрать механизмы трансляции запросов платформы 1С и работы планировщика Microsoft SQL Server (или другой используемой СУБД).
1. Архитектура критериев отбора в базе данных
Объект метаданных Критерий отбора не имеет под собой реальной физической таблицы в СУБД. Это виртуальный объект. Когда платформа 1С выполняет запрос к таблице критерия отбора, она на лету генерирует огромный SQL-запрос, представляющий собой объединение множества физических таблиц через конструкцию UNION ALL.
В крупных типовых конфигурациях (таких как ERP, УПП, Комплексная автоматизация) критерий СвязанныеДокументы может включать в себя от 50 до 150 различных типов документов и их табличных частей. Соответственно, итоговый запрос к СУБД превращается в колоссальную конструкцию из ста и более объединенных подзапросов.
2. Влияние инструкции ПЕРВЫЕ 1 (TOP 1) на планировщик запросов
Когда запрос выполняется без ограничения количества строк, СУБД строит относительно простой и линейный план выполнения. Она понимает, что нужно просто последовательно пройтись по указанным таблицам.
Но как только добавляется ограничение ПЕРВЫЕ 1 (транслируемое в SQL как TOP 1 на внешнем уровне), оптимизатор СУБД пытается кардинально перестроить план выполнения. Он пытается применить правила оптимизации: «протолкнуть» инструкцию TOP внутрь каждого подзапроса структуры UNION ALL, пересчитать статистику распределения данных и построить вложенные циклы.
Из-за экстремальной сложности дерева такого запроса оптимизатор СУБД (особенно часто этим страдает MS SQL Server) упирается в лимиты выделенной памяти или глубины стека вызовов. Это приводит к возникновению критических внутренних ошибок планировщика СУБД:
3. Почему падает сама платформа 1С?
При возникновении таких критических сбоев СУБД экстренно разрывает сетевое соединение с клиентом (в данном случае — с сервером приложений 1С). В старых релизах платформы (например, в ветке 8.3.13) ядро платформы не всегда может корректно обработать внезапную «смерть» сессии СУБД во время построения плана запроса, что и приводит к падению рабочего процесса rphost или закрытию клиентского сеанса.
4. Влияние механизма ограничений доступа (RLS)
Если подобный запрос выполняется не в привилегированном модуле, а под обычным пользователем с включенным RLS, ситуация усугубляется многократно. Платформа будет вынуждена добавить условия проверки прав к каждой ветке UNION ALL, что увеличит количество задействованных таблиц и условий в геометрической прогрессии, гарантированно приводя к краху СУБД.
Рассмотрим подробнее по шагам, как можно обойти эту архитектурную проблему без потери производительности.
Это самый простой и надежный способ обойти ограничения оптимизатора СУБД. Суть метода заключается в том, чтобы разделить сложную операцию на два простых этапа:
ПЕРВЫЕ 1. Поскольку нет ограничения TOP 1, оптимизатор СУБД легко построит простой линейный план выполнения и мгновенно запишет ссылки во временную таблицу.ПЕРВЫЕ 1 уже из созданной временной таблицы. Так как временная таблица является плоской физической таблицей в БД, этот запрос отработает за доли миллисекунды.Посмотрим на пример реализации этого решения:
Функция КритерииОтбора__ЕстьСвязанныеДокументы_Надежная(СсылкаНаОбъект) Экспорт
Результат = Ложь;
Если НЕ ЗначениеЗаполнено(СсылкаНаОбъект) Тогда
Возврат Результат;
КонецЕсли;
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("СсылкаНаОбъект", СсылкаНаОбъект);
// Помещаем данные во временную таблицу ВТ_Связи без ПЕРВЫЕ 1
Запрос.Текст =
"ВЫБРАТЬ
| Т.Ссылка КАК Ссылка
|ПОМЕСТИТЬ ВТ_Связи
|ИЗ
| КритерийОтбора.СвязанныеДокументы(&СсылкаНаОбъект) КАК Т
|ГДЕ
| НЕ Т.Ссылка.ПометкаУдаления
|;
|
|////////////////////////////////////////////////////////////////////////////////
|// Теперь выбираем ПЕРВЫЕ 1 из временной таблицы
|ВЫБРАТЬ ПЕРВЫЕ 1
| ВТ.Ссылка
|ИЗ
| ВТ_Связи КАК ВТ";
Результат = НЕ Запрос.Выполнить().Пустой();
Возврат Результат;
КонецФункции
Хотя кажется, что выгрузка всех записей во временную таблицу может замедлить работу, на практике количество реальных связанных документов для одного объекта редко превышает десяток. Производительность такого решения будет идеальной, а главное — СУБД больше никогда не упадет.
Если в вашей системе критерий отбора связан со множеством документов, но реальные связи чаще всего возникают лишь с 2–3 основными типами документов (например, РеализацияТоваровУслуг, СчетФактураВыданный, ЗаказПокупателя), правильнее отказаться от универсального критерия отбора в пользу прямого точечного запроса.
Проанализируем пример такого подхода:
Запрос = Новый Запрос;
Запрос.УстановитьПараметр("СсылкаНаОбъект", СсылкаНаОбъект);
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1 Ссылка ИЗ (
| ВЫБРАТЬ Ссылка ИЗ Документ.РеализацияТоваровУслуг ГДЕ Сделка = &СсылкаНаОбъект И НЕ ПометкаУдаления
| ОБЪЕДИНИТЬ ВСЕ
| ВЫБРАТЬ Ссылка ИЗ Документ.СчетФактураВыданный ГДЕ ДокументОснование = &СсылкаНаОбъект И НЕ ПометкаУдаления
|) КАК Объединение";
Этот запрос содержит минимальное количество объединений, благодаря чему СУБД строит оптимальный план мгновенно и без сбоев.
Зачастую конфигурации развиваются годами, и в критерий СвязанныеДокументы разработчики продолжают добавлять все новые и новые документы, включая устаревшие, архивные или неиспользуемые.
Рекомендуется зайти в конфигуратор и провести ревизию состава критерия отбора. Исключение неиспользуемых метаданных уменьшит количество веток UNION ALL в генерируемом SQL-коде, что значительно облегчит жизнь планировщику СУБД.
Если вы используете устаревшие релизы платформы (такие как 8.3.13 или ранние сборки 8.3.14), настоятельно рекомендуется выполнить обновление. В более современных версиях платформы 1С разработчики улучшили транслятор запросов и оптимизировали генерацию SQL-кода при обращении к виртуальным таблицам критериев отбора. Также полезно установить последние накопительные обновления (Cumulative Updates) для вашей СУБД SQL Server, так как Microsoft регулярно исправляет ошибки оптимизатора, связанные со сложными планами UNION ALL.
Используйте предложенные методики, отдавая предпочтение Решению №1 как наиболее быстрому и универсальному варианту обхода системной ошибки.