Как обойти ограничения выборки данных в запросе 1С 8.3
Содержание
Выбрать из запроса можно хоть миллион строк — жёсткого лимита «не больше N записей» в 1С 8.3 нет. Упираются не в запрет платформы, а в три вещи: оперативную память, время выполнения и блокировки таблиц. Когда выборка «не обходится», чаще всего клиенту не хватает памяти или запрос висит минутами на большой базе.
Поэтому «обход ограничений выборки» на практике — это не обход защиты, а грамотная техника чтения больших результатов: порциями, через временные таблицы, с правильными индексами. Разберём легальные методы, альтернативные подходы и ошибки, из-за которых запрос тормозит там, где мог бы работать за секунды.
Что на самом деле ограничивает выборку из запроса
Главное заблуждение: будто Выбрать() затягивает весь результат в память. Это не так.
Объект выборки — это курсор. Строки читаются из СУБД по одной, по мере вызова Следующий(). Перебор даже миллиона записей через выборку памяти почти не ест — сервер отдаёт данные потоком.
Память «съедает» другое:
- 📥
Результат.Выгрузить()— вот он действительно грузит весь результат в таблицу значений целиком. - 🧱 накопление объектов в переменной: если в цикле складывать прочитанные документы или структуры в массив, память растёт линейно.
- 🔁 обращение к реквизитам через точку (
Выборка.Ссылка.Реквизит) — каждая точка в цикле дочитывает объект из базы отдельным обращением.
Реальные ограничения, с которыми сталкивается разработчик:
| Что ограничивает | В чём проявляется | Как обойти |
|---|---|---|
| Память клиента | «Недостаточно памяти» при Выгрузить() | читать курсором или порциями |
| Время выполнения | запрос висит, таймаут соединения | индексы, временные таблицы |
| Блокировки | другие сеансы ждут завершения | порции + короткие транзакции |
| Нагрузка на СУБД | тормозит вся база в рабочее время | фоновая обработка в тихие часы |
Отдельно про таймауты и блокировки. Таймаут соединения с СУБД обрывает запрос, который выполняется слишком долго, — и тогда кажется, будто «выборка не прошла», хотя дело в отсутствии индекса. Блокировки же возникают, когда тяжёлый запрос в управляемом или автоматическом режиме держит таблицы, а другие сеансы ждут их освобождения. На многопользовательской базе медленная выборка бьёт не только по вашему сеансу.
Прежде чем «обходить» ограничение, замерьте, в какое именно вы упёрлись. Техника для нехватки памяти и для медленного запроса — разная. Для нехватки памяти — читать курсором или порциями; для медленного запроса — индексы и временные таблицы.
Чтение результата порциями: обход больших выборок
Если нужно обработать всю таблицу и памяти под объекты не хватает, читайте порциями по 1000–10000 записей. Приём простой: берём ВЫБРАТЬ ПЕРВЫЕ N, упорядочиваем по уникальному ключу и на каждой итерации сдвигаем границу.
ГраницаСсылки = Справочники.Номенклатура.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Товары.Ссылка КАК Ссылка,
| Товары.Наименование КАК Наименование
|ИЗ
| Справочник.Номенклатура КАК Товары
|ГДЕ
| Товары.Ссылка > &ГраницаСсылки
|УПОРЯДОЧИТЬ ПО
| Товары.Ссылка";
Пока Истина Цикл
Запрос.УстановитьПараметр("ГраницаСсылки", ГраницаСсылки);
Выборка = Запрос.Выполнить().Выбрать();
ЕстьДанные = Ложь;
Пока Выборка.Следующий() Цикл
ЕстьДанные = Истина;
ГраницаСсылки = Выборка.Ссылка;
// обработка одной строки
КонецЦикла;
Если Не ЕстьДанные Тогда
Прервать;
КонецЕсли;
КонецЦикла;
Границу двигаем по полю с гарантированно уникальным и упорядочиваемым значением. Для документов надёжнее упорядочивать по паре Дата, Ссылка — тогда порядок осмысленный и при равных датах записи не теряются и не дублируются.
Если каждую порцию нужно ещё и записать обратно (пересчитать реквизит, перепровести), фиксируйте изменения отдельной транзакцией на порцию, а не одной на весь объём. Так блокировки держатся секунды, а не часы, и при сбое откатывается только последняя порция, а не вся работа.
Не используйте для порций Смещение/нумерацию строк вручную: при вставке новых записей границы «съедут», и часть данных вы пропустите. Метод с границей ключа от этого защищён.
Для иерархических и сгруппированных результатов есть встроенный обход по уровням:
Результат = Запрос.Выполнить();
ВыборкаГрупп = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГрупп.Следующий() Цикл
ВыборкаДетальных = ВыборкаГрупп.Выбрать();
Пока ВыборкаДетальных.Следующий() Цикл
// строки внутри группы
КонецЦикла;
КонецЦикла;
Временные таблицы и пакетные запросы
Когда данные надо несколько раз соединять, фильтровать и агрегировать, не делайте это одним гигантским запросом с вложенными подзапросами. Разложите логику на пакет: сначала положите промежуточный набор во временную таблицу, потом работайте с ней.
Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
"ВЫБРАТЬ
| Продажи.Номенклатура КАК Номенклатура,
| Продажи.Количество КАК Количество
|ПОМЕСТИТЬ ВТ_Продажи
|ИЗ
| РегистрНакопления.Продажи КАК Продажи
|ГДЕ
| Продажи.Период МЕЖДУ &Начало И &Конец
|ИНДЕКСИРОВАТЬ ПО
| Номенклатура
|;
|ВЫБРАТЬ
| ВТ_Продажи.Номенклатура КАК Номенклатура,
| СУММА(ВТ_Продажи.Количество) КАК Количество
|ИЗ
| ВТ_Продажи КАК ВТ_Продажи
|СГРУППИРОВАТЬ ПО
| ВТ_Продажи.Номенклатура";
Что это даёт:
- 🧮 СУБД считает промежуточный результат один раз, а не пересчитывает подзапрос при каждом соединении;
- 🔍 на временную таблицу можно повесить индекс (
ИНДЕКСИРОВАТЬ ПО) — соединение с ней идёт по индексу, а не перебором; - 🧩 сложная выборка читается кусками и легче оптимизируется.
Менеджер временных таблиц живёт в памяти рабочего процесса на сервере. Не держите в нём десятки огромных таблиц дольше, чем нужно, — освобождайте его после использования.
Оптимизация запроса: как уменьшить объём выборки
Самый дешёвый «обход» — не выбирать лишнего. Часто проблему больших выборок решает не хитрый код, а переписанный запрос.
- 🎯 Фильтруйте в виртуальных таблицах, а не после. Условие по периоду и измерениям передавайте в параметры виртуальной таблицы (
Остатки(&Дата, Склад = &Склад)), а не наложениемГДЕна итоговую выборку. - 🧷 Выбирайте только нужные поля.
ВЫБРАТЬ *и десяток реквизитов «на всякий случай» раздувают трафик между СУБД и сервером 1С. - 🚫 Избегайте
ПОДОБНО "%текст%". Поиск с ведущим процентом не использует индекс и приводит к полному сканированию таблицы. - 🧱 Не соединяйте с вложенными запросами. Вынесите подзапрос во временную таблицу с индексом.
- 🔢 Приводите типы через
ВЫРАЗИТЬдля полей составного типа — иначе СУБД читает все таблицы, входящие в тип.
Ещё несколько приёмов, которые убирают лишние строки до того, как они попадут в выборку:
- 📅 Срез последних и срез первых для регистров сведений берите параметрами виртуальной таблицы, а не группировкой с максимумом периода вручную — платформа сделает это эффективнее.
- 🧮 Соединяйте от меньшей таблицы к большей. Порядок источников в соединении влияет на план: сначала узкий отобранный набор, потом крупная таблица по индексу.
- 🔁 Не дёргайте одни и те же данные дважды. Если промежуточный результат нужен в нескольких частях пакета — положите его во временную таблицу один раз.
Проверить план помогает технологический журнал и замер времени через ТекущаяУниверсальнаяДатаВМиллисекундах() до и после Выполнить(). Если запрос к регистру долгий — почти всегда дело в отсутствии подходящего индекса или в фильтре, наложенном после соединения. Не оптимизируйте вслепую: сначала найдите самый медленный запрос замером, потом правьте именно его.
Когда запрос — не лучший инструмент: альтернативные подходы
Запрос — не единственный способ достать данные. Иногда правильнее не «обходить выборку», а вообще не строить её.
- 🖥 Динамический список на форме. Если данные нужно показать пользователю, не читайте их в код. Динамический список грузит с сервера только видимую порцию строк и сам подгружает остальное при прокрутке — хоть на миллионной таблице форма открывается мгновенно.
- 📇 Менеджеры записей и объектное чтение. Для точечной работы с одной записью регистра или объектом справочника читайте его напрямую (
СоздатьНаборЗаписей,ПолучитьОбъект), без запроса. - 🔗 Внешние источники данных. Для разовых аналитических выгрузок из чужой базы можно подключить внешний источник и читать его SQL-таблицы запросом 1С — но только на чтение.
Прямая запись в таблицы базы 1С в обход платформы (через SQL-клиент, ADO и т.п.) ломает целостность данных, снимает базу с поддержки и приводит к непредсказуемым ошибкам. Читать напрямую для анализа — допустимо на свой риск; писать — нельзя.
Какой подход выбрать под вашу задачу:
Обработка базы на миллион записей и больше
На объёмах от сотен тысяч строк к порционному чтению добавляются ещё два правила: не блокировать пользователей и не делать всё в одном потоке.
- Короткие транзакции. Не оборачивайте обработку всего массива в одну транзакцию — она будет держать блокировки часами и раздувать журнал транзакций СУБД. Фиксируйте порциями: обработали 1000 записей —
ЗафиксироватьТранзакцию(), начали следующую. - Фоновые задания. Длительную обработку запускайте через
ФоновыеЗадания.ВыполнитьВФоне()(или механизм длительных операций из БСП). Пользователь не ждёт, а выполнение идёт на сервере. - Разбиение диапазона. Миллион записей можно поделить по периодам (по месяцам) или по диапазонам ключа и запустить несколько фоновых заданий параллельно — на многоядерном сервере это кратно быстрее.
- Тихие часы. Тяжёлый пересчёт планируйте регламентным заданием на ночь. Массовая обработка в рабочее время тормозит всю базу: пользователи упираются в блокировки и ждут.
Полезно вести учёт прогресса: запоминайте границу последней обработанной порции в регистре сведений или реквизите, чтобы прерванную обработку можно было продолжить с места остановки, а не начинать миллион записей заново.
Перед массовой обработкой боевой базы сделайте резервную копию и прогоните алгоритм на копии. Ошибка в цикле на миллионе записей исправляется долго.
Типичные ошибки при обходе выборки
Эти грабли встречаются чаще всего — и все они про производительность, а не про запреты платформы.
- ❌ Запрос в цикле. Классика: перебирают строки одной выборки и внутри на каждую выполняют отдельный запрос. Вместо тысяч мелких запросов сделайте один с соединением или временной таблицей.
- ❌ Обращение к реквизитам через точку в цикле.
Выборка.Ссылка.Договор.Организацияв цикле — это два скрытых дочитывания объекта на каждую строку. Добавьте нужные поля прямо в запрос. - ❌
Выгрузить()всего результата. Выгружать в таблицу значений имеет смысл небольшой итог, а не сырой миллион строк. Для перебора хватает курсора. - ❌ Отсутствие индекса на временной таблице. Соединение с неиндексированной временной таблицей на больших объёмах превращается в полный перебор.
- ❌ Фильтр после соединения вместо условия в виртуальной таблице. Сначала тянут все остатки, потом отбрасывают лишнее — вместо того чтобы попросить у регистра сразу нужный срез.
- ❌ Преждевременная оптимизация. Переписывать на порции и фоновые задания запрос, который и так отрабатывает за доли секунды, — потраченное время. Сложные приёмы нужны там, где замер показал реальную проблему.
Частые вопросы
Есть ли в 1С лимит на количество строк в запросе?
Следующий() работает и на миллионах строк.Почему `Выбрать()` не грузит всё в память, а `Выгрузить()` грузит?
Выбрать() возвращает курсор: строки читаются из СУБД по одной. Выгрузить() же материализует весь результат в таблицу значений сразу — отсюда и расход памяти на больших выборках.