Каждый разработчик 1С рано или поздно сталкивается с мистической ситуацией: запрос гарантированно возвращает данные, метод Результат.Пустой() возвращает значение Ложь, выборка в цикле успешно обходится, но при попытке выгрузить результат в таблицу значений с помощью метода Выгрузить() отладчик показывает абсолютно пустой объект — без единой строки и даже без колонок. В этой статье мы подробно разберем, почему возникает этот эффект, как работает современный механизм отладки в платформе 1С 8.3, а также детально сравним производительность и потребление памяти при использовании методов Выбрать() и Выгрузить().
Рассмотрим классическую ситуацию, с которой столкнулся автор темы. Нам необходимо получить остатки товаров на складах. Мы пишем простой и понятный код:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ РАЗЛИЧНЫЕ
| ОстаткиТоваровКомпанииОстатки.Номенклатура,
| ОстаткиТоваровКомпанииОстатки.СкладКомпании,
| ОстаткиТоваровКомпанииОстатки.КоличествоОстаток
|ИЗ
| РегистрНакопления.ОстаткиТоваровКомпании.Остатки(, ) КАК ОстаткиТоваровКомпанииОстатки";
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Сообщить("Нет товаров на учете!");
Возврат;
Иначе
ТЗ = Результат.Выгрузить();
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
// Проверяем наличие данных во время обхода выборки
А = Выборка[0];
КонецЦикла;
Останавливая выполнение программы точкой останова на цикле обхода выборки, мы видим, что переменная А успешно заполняется значениями. Это доказывает, что в результате запроса действительно есть строки. Однако, если мы попытаемся открыть переменную ТЗ в табло отладки или через вычислитель выражений, мы увидим пустую таблицу без колонок. Проанализируем, почему это происходит.
Выясним причину такого странного поведения платформы. Начиная с версии 8.3, фирма «1С» перевела механизм отладки на протокол HTTP (вместо старого TCP/IP) и внедрила концепцию асинхронного получения тяжелых данных.
В старых версиях платформы (8.2 и ранее) при попытке просмотреть большую таблицу значений в отладчике Конфигуратор полностью блокировал интерфейс (зависал), пока все данные не передавались с сервера приложений на локальный компьютер разработчика. Если таблица содержала сотни тысяч строк, это могло привести к аварийному завершению работы Конфигуратора.
В современных версиях платформы 1С 8.3 эта проблема решена следующим образом:
ТаблицаЗначений, отладчик мгновенно отображает ее пустую «оболочку».Именно поэтому при первом открытии ТЗ в отладчике вы видите пустой объект. Если подождать некоторое время (от нескольких секунд до минуты в зависимости от объема данных и скорости соединения), таблица заполнится строками и колонками автоматически.
Если вам не хочется ждать фоновой отрисовки всей таблицы, мы рекомендуем использовать простые и надежные способы проверки реального состояния объекта:
ТЗ, добавьте в табло выражение ТЗ.Количество(). Это свойство вычисляется мгновенно на стороне сервера и возвращает точное число строк.Выборка.Владелец().Выгрузить().Количество(), чтобы убедиться, что выгрузка работает корректно.В процессе обсуждения данной проблемы разработчики часто затрагивают тему стандартов разработки и спорят о том, какой метод использовать правильнее — Выбрать() или Выгрузить(). Разберем этот вопрос подробно.
Существует популярное заблуждение, что метод Выбрать() работает по принципу «курсора СУБД» и читает данные с сервера порциями, экономя оперативную память, в то время как Выгрузить() сразу загружает всю таблицу в ОЗУ. Это миф.
Согласно официальным стандартам разработки фирмы «1С», при вызове метода Выполнить() результат любого запроса всегда считывается в оперативную память сервера приложений целиком. Разница между методами заключается лишь в том, как эти данные представляются на уровне встроенного языка 1С:
Выбрать() возвращает объект ВыборкаИзРезультатаЗапроса. Это легковесная объектная обертка над уже находящимися в памяти сервера данными. Она не дублирует строки и тратит минимум ресурсов.Выгрузить() создает полноценный тяжелый объект типа ТаблицаЗначений. Платформа вынуждена заново скопировать все данные из внутреннего представления результата запроса в структуру таблицы значений, создать колонки, типы данных и системные индексы.Проанализируем результаты замеров производительности на реальном примере (10 000 строк, в каждой строке длинное текстовое поле объемом около 245 Кб):
Выбрать()): Потребление памяти процессом — 4 465 048 Кб, время выполнения операции — 11 секунд.Выгрузить()): Потребление памяти процессом — 4 469 572 Кб, время выполнения операции — 16 секунд.Как мы видим, потребление памяти отличается незначительно (всего на несколько мегабайт служебных данных ТЗ), однако время выполнения операции при выгрузке увеличивается почти на 45%. На огромных объемах данных выгрузка в ТЗ создает ощутимую дополнительную нагрузку на процессор сервера приложений.
Рассмотрим лучшие практики применения обоих подходов:
Используйте метод Выбрать(), если:
Выбрать() поддерживает обход по группировкам с сохранением иерархии).Используйте метод Выгрузить(), если:
НайтиСтроки()).Свернуть()), отсортировать их по произвольному набору колонок на клиенте или передать таблицу в качестве параметра в другие процедуры и функции.В ходе дискуссии был поднят вопрос об удалении строк из таблицы значений по сложным условиям (например, при сравнении данных запроса с внешним файлом Excel). Рассмотрим, как делать это правильно.
Удаление строк из большой таблицы значений в цикле — это крайне неоптимальная операция. При удалении каждой строки платформа сдвигает все последующие элементы в памяти, что приводит к квадратичному росту времени работы алгоритма. Если вам необходимо отфильтровать данные, мы рекомендуем следующие подходы:
Скопировать(), передав в него структуру отбора. Скопировать нужные строки в новую таблицу всегда быстрее, чем удалять ненужные строки из старой.