Есть такой код, который выглядит безобидно. Обращение через точку: Документ.Контрагент.ИНН. Три уровня, одна строка, никаких запросов. Платформа сама сходит в базу и вернёт значение. Проблема в том, что она сходит в базу каждый раз.
Что произошло
Обработка массовой печати документов реализации. Цикл по выборке: 800 документов за месяц. Для каждого нужны реквизиты контрагента — ИНН, КПП, наименование, юридический адрес. И реквизиты организации — те же поля плюс банковские данные.
Код написан чисто, читабельно, без единого запроса. Всё через точечную нотацию:
Документ.Контрагент.Наименование, Документ.Контрагент.ИНН, Документ.Организация.НаименованиеПолное, Документ.Организация.Префикс — и так двенадцать полей.
Обработка выполнялась двенадцать минут. Для 800 документов это катастрофа.
Почему точка — это скрытый запрос
Каждое обращение через точку к реквизиту ссылочного типа — это отдельный SELECT к базе данных. Платформа 1С не группирует их. Не кэширует между итерациями цикла. Не оптимизирует.
Документ.Контрагент — это один запрос: получить ссылку на контрагента из документа. Документ.Контрагент.ИНН — это два запроса: сначала получить ссылку, потом прочитать ИНН из справочника. Если в следующей строке стоит Документ.Контрагент.КПП — ещё два запроса. Даже если контрагент тот же самый.
Двенадцать полей через точку в цикле из 800 итераций. Каждое поле — минимум два обращения к базе. Итого: 800 × 12 × 2 = 19 200 запросов к SQL Server. Для печати документов.
На маленьких выборках это незаметно. Пять документов — полсекунды. Пятьдесят — пять секунд. Восемьсот — двенадцать минут. Зависимость нелинейная: чем больше данных в кэше SQL Server вытесняется, тем медленнее каждый следующий запрос.
Решение: один запрос вместо тысяч
Идея та же, что и в любой задаче оптимизации 1С: не ходить в базу в цикле. Заранее собрать всё, что нужно, одним запросом — и работать с результатом в памяти.
Перед циклом строим запрос: ВЫБРАТЬ из документов реализации за период все нужные поля через левое соединение со справочниками контрагентов и организаций. Один запрос, одно обращение к базе. Результат — таблица значений с 800 строками и всеми двенадцатью полями.
В цикле вместо обращений через точку — чтение из подготовленной таблицы. Никаких запросов к SQL. Вся работа в оперативной памяти.
Время выполнения: 50 секунд. В 14 раз быстрее.
Но есть нюанс. Не всегда можно заменить точечную нотацию запросом. Если обрабатываются разные типы документов с разной структурой — запрос усложняется. Если нужны реквизиты из табличных частей — нужен отдельный запрос для них. Если в цикле есть ветвление (для одних документов нужны одни поля, для других — другие) — запрос превращается в монстра.
В таких случаях помогает промежуточный вариант: загрузить ссылочные данные пакетом через ВЫБРАТЬ...ГДЕ...В(&СписокСсылок) и положить в соответствие. По ключу — ссылка, по значению — структура с нужными реквизитами. В цикле обращаемся к соответствию — это мгновенно.
Как обнаружить проблему
Точечную нотацию в цикле сложно поймать на код-ревью, если не знать, что искать. Код выглядит чистым. Никаких явных запросов, никаких конструкций Запрос = Новый Запрос. Всё элегантно и читаемо.
Два надёжных способа:
1. Замер производительности. Встроенный в платформу инструмент. Включается через Сервис → Параметры → Замер производительности. Показывает время выполнения каждой строки кода. Если строка с обращением через точку занимает больше миллисекунды в цикле — это сигнал.
2. Технологический журнал. Событие DBMSSQL (или DBPOSTGRS для PostgreSQL). Включаем журнал с фильтром по длительности — например, все запросы дольше 0 микросекунд за время выполнения обработки. Если видим сотни одинаковых коротких SELECT к одной таблице — значит, где-то в коде точечная нотация в цикле.
Третий способ — самый простой. Откройте код и поищите конструкции вида СсылкаНаОбъект.РеквизитСсылочногоТипа.Реквизит внутри циклов Для Каждого, Пока, Для. Если нашли — вероятность проблемы высокая.
Типичные возражения и ответы
«Платформа кэширует обращения через точку». Да, в пределах одного программного контекста есть кэш объектов. Но он ограничен. При 800 итерациях с разными ссылками кэш вытесняется, и платформа идёт в базу заново. На практике кэш помогает, когда в цикле обращаешься к одному и тому же объекту несколько раз подряд. Между итерациями — не помогает.
«Запрос сложнее писать и поддерживать». Да. Но двенадцать минут ожидания — это не поддержка, это инцидент. Компромисс: вынести запрос в отдельную функцию ПолучитьДанныеДляПечати(МассивДокументов), которая возвращает таблицу значений. Цикл печати работает с таблицей, не знает про базу данных.
«У нас мало документов, это не актуально». Сегодня мало. Через год — в три раза больше. Через два — в десять. Базы 1С растут быстро. Код, который сейчас работает секунду, через год будет работать минуту. Лучше писать правильно сразу.
Правило: в цикле не должно быть обращений к базе
Не только точечная нотация. Любое обращение к базе внутри цикла — потенциальная проблема. Получить реквизит через точку. Выполнить запрос. Прочитать регистр сведений через менеджер. Обратиться к общему реквизиту. Всё это порождает SQL-запросы.
Правило простое: собери данные до цикла, обработай в цикле, запиши результат после цикла. Три фазы. Фаза чтения, фаза вычислений, фаза записи. Между ними — только работа в памяти.
Мы видим точечную нотацию в циклах буквально в каждом проекте, куда приходим на аудит производительности. Не потому что разработчики не знают. Знают. Но точка короче запроса, IDE не подчёркивает её красным, и на тестовой базе с десятью документами всё летает. А потом клиент звонит и говорит: «Печать реализаций зависла, бухгалтерия стоит».


