Перебор всех документов в 1С: от выборки до фоновых заданий

· 9 мин

Содержание

Перебрать документы в 1С можно тремя способами: методом Выбрать() менеджера конкретного вида, запросом по языку запросов и обходом коллекции Метаданные.Документы, когда нужно пройтись по всем видам сразу. Для разовой правки десятка документов хватит выборки, для сотен тысяч — только запрос с порционной обработкой в фоновом задании.

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

Какой способ перебора выбрать

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

  • 🔹 До нескольких сотен документов, нужны реквизиты и табличные части — метод Выбрать() менеджера. Просто и читаемо.
  • 🔹 Нужны только ссылки для последующей обработки — запрос, выбирающий поле Ссылка. Экономит память.
  • 🔹 Пройтись по всем видам документов конфигурации — цикл по Метаданные.Документы.
  • 🔹 Десятки и сотни тысяч записей — запрос + порционная выборка + фоновое задание.
Сколько документов и что с ними делать?

Перебор через метод Выбрать() менеджера документа

Самый прямой способ обойти документы одного вида — метод Выбрать() у менеджера. Он возвращает объект ВыборкаДокументов, по которому идём циклом Пока ... Цикл.

Выборка = Документы.РеализацияТоваровУслуг.Выбрать();
Пока Выборка.Следующий() Цикл
    Сообщить(Выборка.Номер + " от " + Выборка.Дата);
КонецЦикла;

Внутри цикла доступны все реквизиты шапки как поля выборки: Выборка.Дата, Выборка.Номер, Выборка.Контрагент. Это чтение — редактировать данные напрямую здесь нельзя.

Чтобы изменить и записать документ, получите объект:

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

Метод Выбрать() без параметров тянет все документы вида в одном проходе. На таблице в сотни тысяч строк это долгий запрос и риск нехватки памяти. Для больших объёмов используйте запрос с порциями (раздел ниже).

У метода есть параметры отбора и интервала дат:

Отбор = Новый Структура("Проведен", Ложь);
Выборка = Документы.РеализацияТоваровУслуг.Выбрать(
    '20250101', '20251231', Отбор);

Так переберём только непроведённые документы за год. Отбор по структуре работает лишь по индексируемым реквизитам; для сложных условий берите запрос.

Обход всех видов документов через метаданные

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

Для Каждого ОписаниеДок Из Метаданные.Документы Цикл
    Запрос = Новый Запрос;
    Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Документ." + ОписаниеДок.Имя;
    Выборка = Запрос.Выполнить().Выбрать();
    Пока Выборка.Следующий() Цикл
        // обработка Выборка.Ссылка
    КонецЦикла;
КонецЦикла;

Здесь ОписаниеДок.Имя подставляется в текст запроса, формируя имя таблицы Документ.ИмяВида. Так код не привязан к конкретным видам и сам подхватит новые документы, добавленные в конфигурацию.

💡 Если нужны не все виды, а по признаку — отфильтруйте по имени или подсистеме. Например, обработать только документы, входящие в определённую подсистему, можно через ОписаниеДок.Подсистемы или проверкой вхождения имени в заранее заданный список.

Полностью универсальный обход всех ссылочных объектов (не только документов) строят через Метаданные.НайтиПоПолномуИмени(), но для документов достаточно коллекции Метаданные.Документы — она явнее и безопаснее.

У каждого описания метаданных доступны и реквизиты состава: ОписаниеДок.Реквизиты, ОписаниеДок.ТабличныеЧасти. Это удобно, когда перебор завязан не на вид документа, а на наличие конкретного реквизита — например, нужно заполнить поле «Ответственный» во всех документах, где оно есть. Тогда перед запросом проверяют ОписаниеДок.Реквизиты.Найти("Ответственный") <> Неопределено и пропускают виды, у которых реквизита нет.

Массовая выборка документов запросом

Для массовой обработки запрос почти всегда лучше метода Выбрать(): вы берёте из базы ровно то, что нужно, и не поднимаете в память реквизиты и табличные части целиком.

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

Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
    Объект = Выборка.Ссылка.ПолучитьОбъект();
    // правки
    Объект.Записать();
КонецЦикла;

Важные моменты по языку запросов при переборе:

  • Выбирайте только Ссылка, если дальше всё равно получаете объект. Лишние поля в выборке замедляют запрос.
  • Все условия пишите в секции ГДЕ через параметры (&Имя), а не фильтруйте в цикле на встроенном языке — отбор в запросе уходит на сторону СУБД.
  • Никогда не склеивайте значения в текст запроса конкатенацией — только через УстановитьПараметр(). Иначе получите ошибки типов и дыры в выборке.

Запрос возвращает ссылки в произвольном порядке, если не задан УПОРЯДОЧИТЬ ПО. Для порционной обработки порядок обязателен — об этом дальше.

Если по каждому документу нужны ещё и строки табличной части, не получайте объект в цикле ради них — добавьте второй набор данных в тот же запрос. Табличную часть выбирают как Документ.РеализацияТоваровУслуг.Товары с полем Ссылка, а результаты группируют методом Выгрузить() в дерево или связывают по ссылке. Один запрос с соединением почти всегда быстрее, чем тысячи вызовов ПолучитьОбъект() только ради чтения строк.

Порционная обработка больших объёмов

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

Порции нарезаем по ссылке: выбираем первые N, у которых ссылка больше последней обработанной. Так не нужно хранить смещение и не теряются записи.

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

Пока Истина Цикл
    Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылка);
    Результат = Запрос.Выполнить();
    Если Результат.Пустой() Тогда
        Прервать;
    КонецЕсли;
    Выборка = Результат.Выбрать();
    Пока Выборка.Следующий() Цикл
        Объект = Выборка.Ссылка.ПолучитьОбъект();
        // правки
        Объект.Записать();
        ПоследняяСсылка = Выборка.Ссылка;
    КонецЦикла;
КонецЦикла;

Такой перебор всех документов не зависит от размера таблицы по памяти: в каждый момент в работе одна порция. Размер порции подбирают опытно — 500–1000 для документов с проведением, до 5000 для простой правки реквизита.

Перед массовой обработкой документов
Выполнено: 0 / 6

Вынос тяжёлой обработки в фоновое задание

Обработка на минуты и часы не должна висеть в сеансе пользователя: клиент «подвиснет», а по тайм-ауту соединение оборвётся. Длительный перебор выносят в фоновое задание — отдельный серверный процесс.

Параметры = Новый Массив;
Параметры.Добавить(НачалоГода(ТекущаяДата()));
ФоновыеЗадания.Выполнить(
    "ОбработкаДокументов.ПеребратьРеализации",
    Параметры,
    ,
    "Перепроведение реализаций");

Вызываемая процедура должна лежать в общем модуле с флагом «Сервер» и быть экспортной. Внутри неё — тот самый порционный перебор из предыдущего раздела.

💡 В типовых конфигурациях на БСП есть подсистема «Длительные операции»: она сама запускает фоновое задание, показывает пользователю прогресс-бар и позволяет прервать процесс. Если пишете обработку для типовой базы — используйте её методы вместо «голого» ФоновыеЗадания, не изобретайте индикатор прогресса заново.

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

Транзакции и блокировки при массовой записи

Каждый Объект.Записать() сам по себе — это транзакция плюс блокировки затронутых таблиц и регистров. При переборе всех документов это главная причина взаимоблокировок и жалоб «база висит».

Базовые правила, чтобы массовая запись не мешала работе пользователей:

  1. Не оборачивайте весь цикл в одну транзакцию. Одна транзакция на сотни тысяч записей держит блокировки до самого конца и копит огромный журнал. Фиксируйте порциями.
  2. Группируйте запись пачками через НачатьТранзакцию() / ЗафиксироватьТранзакцию() — по одной транзакции на порцию, а не на документ, и не на всё сразу.
  3. Обязательно закрывайте транзакцию в Попытка, иначе при ошибке останется незавершённая транзакция.
НачатьТранзакцию();
Попытка
    Пока Выборка.Следующий() Цикл
        Объект = Выборка.Ссылка.ПолучитьОбъект();
        Объект.Записать();
    КонецЦикла;
    ЗафиксироватьТранзакцию();
Исключение
    ОтменитьТранзакцию();
    ЗаписьЖурналаРегистрации("Перебор документов", 
        УровеньЖурналаРегистрации.Ошибка, , , 
        ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
    ВызватьИсключение;
КонецПопытки;

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

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

Объект.ОбменДанными.Загрузка = Истина;
Объект.Записать();

Флаг ОбменДанными.Загрузка отключает проверку заполнения и движения по регистрам — применяйте его, только когда уверены, что бизнес-логика проведения в этой задаче не нужна, иначе получите расхождение документов и регистров.

Замер производительности и отладка перебора

Прежде чем оптимизировать перебор, измерьте, что именно тормозит — чаще это не цикл, а запись и проведение внутри него.

Простой замер времени участка — через миллисекунды:

Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
// перебор
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт;
Сообщить("Обработано за " + Длительность / 1000 + " сек");

Инструменты для поиска узких мест:

ИнструментЧто показываетКогда применять
Замер производительности в КонфигуратореВремя по строкам кодаНашли медленный участок в своей обработке
Технологический журналСобытия СУБД, тексты запросов, блокировкиВзаимоблокировки, долгие запросы на сервере
Центр управления производительностью (КИП/БСП)Апдекс, долгие операцииРегулярный мониторинг на рабочей базе

Типичные ошибки, из-за которых перебор документов идёт медленно:

  • ❌ Получение объекта там, где хватило бы данных из выборки запроса (ПолучитьОбъект() в цикле, когда менять ничего не надо).
  • ❌ Отбор не в запросе, а условием Если внутри цикла — база отдаёт лишние строки.
  • ❌ Запись каждого документа в своей транзакции при больших объёмах вместо пачек.
  • ❌ Открытая выборка всей таблицы вместо порций — упирается в память.

При отладке самого цикла ставьте точку останова в отладчике на строке Объект.Записать() и смотрите, на каком документе падает. Для фоновых заданий включите в настройках отладки конфигуратора флаг отладки серверных вызовов и фоновых заданий — иначе точка останова в фоне не сработает.

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

Как перебрать документы только одного вида, но за конкретный период?
Используйте запрос с условием ГДЕ Дата МЕЖДУ &Начало И &Конец и параметрами, либо метод Выбрать(Начало, Конец, Отбор) с датами интервала. Запрос предпочтительнее — отбор уходит на сторону СУБД.
Чем `ПолучитьОбъект()` отличается от работы с выборкой напрямую?
Выборка даёт доступ к реквизитам только на чтение. Чтобы менять и записывать документ, нужен объект — его возвращает Выборка.ПолучитьОбъект() или Ссылка.ПолучитьОбъект(). Получение объекта дороже, поэтому вызывайте его, лишь когда действительно пишете.
Почему массовая обработка блокирует работу пользователей?
Каждая запись документа блокирует его таблицы и затрагиваемые регистры. Если весь перебор идёт в одной транзакции, блокировки держатся до конца. Решение — порции по 500–1000 документов, каждая в своей транзакции, и запуск в нерабочее время.
Как обработать документы всех видов, не перечисляя их вручную?
Цикл по коллекции Метаданные.Документы: для каждого элемента берёте Имя и подставляете в текст запроса Документ.ИмяВида. Код сам подхватит новые виды документов, добавленные в конфигурацию.
Перебор падает по тайм-ауту — что делать?
Длительную операцию нельзя выполнять в клиентском сеансе. Вынесите перебор в фоновое задание (ФоновыеЗадания.Выполнить или подсистема «Длительные операции» БСП) и обрабатывайте порциями, сохраняя ссылку последнего обработанного документа, чтобы продолжить после обрыва.