Как обойти ограничения выборки данных в запросе 1С 8.3

· 9 мин

Содержание

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

Поэтому «обход ограничений выборки» на практике — это не обход защиты, а грамотная техника чтения больших результатов: порциями, через временные таблицы, с правильными индексами. Разберём легальные методы, альтернативные подходы и ошибки, из-за которых запрос тормозит там, где мог бы работать за секунды.

Что на самом деле ограничивает выборку из запроса

Главное заблуждение: будто Выбрать() затягивает весь результат в память. Это не так.

Объект выборки — это курсор. Строки читаются из СУБД по одной, по мере вызова Следующий(). Перебор даже миллиона записей через выборку памяти почти не ест — сервер отдаёт данные потоком.

Память «съедает» другое:

  • 📥 Результат.Выгрузить() — вот он действительно грузит весь результат в таблицу значений целиком.
  • 🧱 накопление объектов в переменной: если в цикле складывать прочитанные документы или структуры в массив, память растёт линейно.
  • 🔁 обращение к реквизитам через точку (Выборка.Ссылка.Реквизит) — каждая точка в цикле дочитывает объект из базы отдельным обращением.

Реальные ограничения, с которыми сталкивается разработчик:

Что ограничиваетВ чём проявляетсяКак обойти
Память клиента«Недостаточно памяти» при Выгрузить()читать курсором или порциями
Время выполнениязапрос висит, таймаут соединенияиндексы, временные таблицы
Блокировкидругие сеансы ждут завершенияпорции + короткие транзакции
Нагрузка на СУБДтормозит вся база в рабочее времяфоновая обработка в тихие часы

Отдельно про таймауты и блокировки. Таймаут соединения с СУБД обрывает запрос, который выполняется слишком долго, — и тогда кажется, будто «выборка не прошла», хотя дело в отсутствии индекса. Блокировки же возникают, когда тяжёлый запрос в управляемом или автоматическом режиме держит таблицы, а другие сеансы ждут их освобождения. На многопользовательской базе медленная выборка бьёт не только по вашему сеансу.

Прежде чем «обходить» ограничение, замерьте, в какое именно вы упёрлись. Техника для нехватки памяти и для медленного запроса — разная. Для нехватки памяти — читать курсором или порциями; для медленного запроса — индексы и временные таблицы.

Чтение результата порциями: обход больших выборок

Если нужно обработать всю таблицу и памяти под объекты не хватает, читайте порциями по 1000–10000 записей. Приём простой: берём ВЫБРАТЬ ПЕРВЫЕ N, упорядочиваем по уникальному ключу и на каждой итерации сдвигаем границу.

ГраницаСсылки = Справочники.Номенклатура.ПустаяСсылка();

Запрос = Новый Запрос;
Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 1000
	|	Товары.Ссылка КАК Ссылка,
	|	Товары.Наименование КАК Наименование
	|ИЗ
	|	Справочник.Номенклатура КАК Товары
	|ГДЕ
	|	Товары.Ссылка > &ГраницаСсылки
	|УПОРЯДОЧИТЬ ПО
	|	Товары.Ссылка";

Пока Истина Цикл
	Запрос.УстановитьПараметр("ГраницаСсылки", ГраницаСсылки);
	Выборка = Запрос.Выполнить().Выбрать();
	ЕстьДанные = Ложь;
	Пока Выборка.Следующий() Цикл
		ЕстьДанные = Истина;
		ГраницаСсылки = Выборка.Ссылка;
		// обработка одной строки
	КонецЦикла;
	Если Не ЕстьДанные Тогда
		Прервать;
	КонецЕсли;
КонецЦикла;

Границу двигаем по полю с гарантированно уникальным и упорядочиваемым значением. Для документов надёжнее упорядочивать по паре Дата, Ссылка — тогда порядок осмысленный и при равных датах записи не теряются и не дублируются.

Если каждую порцию нужно ещё и записать обратно (пересчитать реквизит, перепровести), фиксируйте изменения отдельной транзакцией на порцию, а не одной на весь объём. Так блокировки держатся секунды, а не часы, и при сбое откатывается только последняя порция, а не вся работа.

Не используйте для порций Смещение/нумерацию строк вручную: при вставке новых записей границы «съедут», и часть данных вы пропустите. Метод с границей ключа от этого защищён.

Для иерархических и сгруппированных результатов есть встроенный обход по уровням:

Результат = Запрос.Выполнить();
ВыборкаГрупп = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаГрупп.Следующий() Цикл
	ВыборкаДетальных = ВыборкаГрупп.Выбрать();
	Пока ВыборкаДетальных.Следующий() Цикл
		// строки внутри группы
	КонецЦикла;
КонецЦикла;

Временные таблицы и пакетные запросы

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

Запрос = Новый Запрос;
Запрос.МенеджерВременныхТаблиц = Новый МенеджерВременныхТаблиц;
Запрос.Текст =
	"ВЫБРАТЬ
	|	Продажи.Номенклатура КАК Номенклатура,
	|	Продажи.Количество КАК Количество
	|ПОМЕСТИТЬ ВТ_Продажи
	|ИЗ
	|	РегистрНакопления.Продажи КАК Продажи
	|ГДЕ
	|	Продажи.Период МЕЖДУ &Начало И &Конец
	|ИНДЕКСИРОВАТЬ ПО
	|	Номенклатура
	|;
	|ВЫБРАТЬ
	|	ВТ_Продажи.Номенклатура КАК Номенклатура,
	|	СУММА(ВТ_Продажи.Количество) КАК Количество
	|ИЗ
	|	ВТ_Продажи КАК ВТ_Продажи
	|СГРУППИРОВАТЬ ПО
	|	ВТ_Продажи.Номенклатура";

Что это даёт:

  • 🧮 СУБД считает промежуточный результат один раз, а не пересчитывает подзапрос при каждом соединении;
  • 🔍 на временную таблицу можно повесить индекс (ИНДЕКСИРОВАТЬ ПО) — соединение с ней идёт по индексу, а не перебором;
  • 🧩 сложная выборка читается кусками и легче оптимизируется.

Менеджер временных таблиц живёт в памяти рабочего процесса на сервере. Не держите в нём десятки огромных таблиц дольше, чем нужно, — освобождайте его после использования.

Оптимизация запроса: как уменьшить объём выборки

Самый дешёвый «обход» — не выбирать лишнего. Часто проблему больших выборок решает не хитрый код, а переписанный запрос.

  • 🎯 Фильтруйте в виртуальных таблицах, а не после. Условие по периоду и измерениям передавайте в параметры виртуальной таблицы (Остатки(&Дата, Склад = &Склад)), а не наложением ГДЕ на итоговую выборку.
  • 🧷 Выбирайте только нужные поля. ВЫБРАТЬ * и десяток реквизитов «на всякий случай» раздувают трафик между СУБД и сервером 1С.
  • 🚫 Избегайте ПОДОБНО "%текст%". Поиск с ведущим процентом не использует индекс и приводит к полному сканированию таблицы.
  • 🧱 Не соединяйте с вложенными запросами. Вынесите подзапрос во временную таблицу с индексом.
  • 🔢 Приводите типы через ВЫРАЗИТЬ для полей составного типа — иначе СУБД читает все таблицы, входящие в тип.

Ещё несколько приёмов, которые убирают лишние строки до того, как они попадут в выборку:

  • 📅 Срез последних и срез первых для регистров сведений берите параметрами виртуальной таблицы, а не группировкой с максимумом периода вручную — платформа сделает это эффективнее.
  • 🧮 Соединяйте от меньшей таблицы к большей. Порядок источников в соединении влияет на план: сначала узкий отобранный набор, потом крупная таблица по индексу.
  • 🔁 Не дёргайте одни и те же данные дважды. Если промежуточный результат нужен в нескольких частях пакета — положите его во временную таблицу один раз.

Проверить план помогает технологический журнал и замер времени через ТекущаяУниверсальнаяДатаВМиллисекундах() до и после Выполнить(). Если запрос к регистру долгий — почти всегда дело в отсутствии подходящего индекса или в фильтре, наложенном после соединения. Не оптимизируйте вслепую: сначала найдите самый медленный запрос замером, потом правьте именно его.

Когда запрос — не лучший инструмент: альтернативные подходы

Запрос — не единственный способ достать данные. Иногда правильнее не «обходить выборку», а вообще не строить её.

  • 🖥 Динамический список на форме. Если данные нужно показать пользователю, не читайте их в код. Динамический список грузит с сервера только видимую порцию строк и сам подгружает остальное при прокрутке — хоть на миллионной таблице форма открывается мгновенно.
  • 📇 Менеджеры записей и объектное чтение. Для точечной работы с одной записью регистра или объектом справочника читайте его напрямую (СоздатьНаборЗаписей, ПолучитьОбъект), без запроса.
  • 🔗 Внешние источники данных. Для разовых аналитических выгрузок из чужой базы можно подключить внешний источник и читать его SQL-таблицы запросом 1С — но только на чтение.

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

Какой подход выбрать под вашу задачу:

Как достать большой объём данных?

Обработка базы на миллион записей и больше

На объёмах от сотен тысяч строк к порционному чтению добавляются ещё два правила: не блокировать пользователей и не делать всё в одном потоке.

  1. Короткие транзакции. Не оборачивайте обработку всего массива в одну транзакцию — она будет держать блокировки часами и раздувать журнал транзакций СУБД. Фиксируйте порциями: обработали 1000 записей — ЗафиксироватьТранзакцию(), начали следующую.
  2. Фоновые задания. Длительную обработку запускайте через ФоновыеЗадания.ВыполнитьВФоне() (или механизм длительных операций из БСП). Пользователь не ждёт, а выполнение идёт на сервере.
  3. Разбиение диапазона. Миллион записей можно поделить по периодам (по месяцам) или по диапазонам ключа и запустить несколько фоновых заданий параллельно — на многоядерном сервере это кратно быстрее.
  4. Тихие часы. Тяжёлый пересчёт планируйте регламентным заданием на ночь. Массовая обработка в рабочее время тормозит всю базу: пользователи упираются в блокировки и ждут.

Полезно вести учёт прогресса: запоминайте границу последней обработанной порции в регистре сведений или реквизите, чтобы прерванную обработку можно было продолжить с места остановки, а не начинать миллион записей заново.

Перед массовой обработкой боевой базы сделайте резервную копию и прогоните алгоритм на копии. Ошибка в цикле на миллионе записей исправляется долго.

Типичные ошибки при обходе выборки

Эти грабли встречаются чаще всего — и все они про производительность, а не про запреты платформы.

  • ❌ Запрос в цикле. Классика: перебирают строки одной выборки и внутри на каждую выполняют отдельный запрос. Вместо тысяч мелких запросов сделайте один с соединением или временной таблицей.
  • ❌ Обращение к реквизитам через точку в цикле. Выборка.Ссылка.Договор.Организация в цикле — это два скрытых дочитывания объекта на каждую строку. Добавьте нужные поля прямо в запрос.
  • ❌ Выгрузить() всего результата. Выгружать в таблицу значений имеет смысл небольшой итог, а не сырой миллион строк. Для перебора хватает курсора.
  • ❌ Отсутствие индекса на временной таблице. Соединение с неиндексированной временной таблицей на больших объёмах превращается в полный перебор.
  • ❌ Фильтр после соединения вместо условия в виртуальной таблице. Сначала тянут все остатки, потом отбрасывают лишнее — вместо того чтобы попросить у регистра сразу нужный срез.
  • ❌ Преждевременная оптимизация. Переписывать на порции и фоновые задания запрос, который и так отрабатывает за доли секунды, — потраченное время. Сложные приёмы нужны там, где замер показал реальную проблему.

Частые вопросы

Есть ли в 1С лимит на количество строк в запросе?
Жёсткого лимита нет — выбрать можно сколько угодно. Ограничивают память (при выгрузке в таблицу значений), время выполнения и блокировки. Перебор курсором через Следующий() работает и на миллионах строк.
Почему `Выбрать()` не грузит всё в память, а `Выгрузить()` грузит?
Выбрать() возвращает курсор: строки читаются из СУБД по одной. Выгрузить() же материализует весь результат в таблицу значений сразу — отсюда и расход памяти на больших выборках.
Можно ли читать базу напрямую через SQL в обход платформы?
Для анализа — через внешние источники данных или SQL-клиент, только на чтение и на свой риск. Писать напрямую нельзя: это рушит целостность данных и снимает базу с поддержки.
Сколько записей брать в одной порции?
Обычно 1000–10000. Чем тяжелее обработка каждой строки, тем меньше порция. Точное число подбирайте замером: ориентируйтесь на то, чтобы одна порция обрабатывалась за секунды, а не минуты.
Чем `ВЫБРАТЬ ПЕРВЫЕ` помогает при порционном чтении?
Он ограничивает число строк в одном обращении. В связке с условием по границе ключа и сортировкой это даёт устойчивое чтение порциями без пропусков и дублей.