Перебор всех документов в 1С: от выборки до фоновых заданий
Содержание
Перебрать документы в 1С можно тремя способами: методом Выбрать() менеджера конкретного вида, запросом по языку запросов и обходом коллекции Метаданные.Документы, когда нужно пройтись по всем видам сразу. Для разовой правки десятка документов хватит выборки, для сотен тысяч — только запрос с порционной обработкой в фоновом задании.
Ниже разберём каждый механизм перебора всех документов с рабочим кодом, покажем, как не положить базу блокировками при массовой записи, и как замерить, где именно обработка тормозит. Код приведён для управляемого приложения платформы 8.3, встроенный язык.
Какой способ перебора выбрать
Прежде чем писать цикл, определитесь с объёмом и задачей. От этого зависит, грузить ли документы в память целиком или брать порциями.
- 🔹 До нескольких сотен документов, нужны реквизиты и табличные части — метод
Выбрать()менеджера. Просто и читаемо. - 🔹 Нужны только ссылки для последующей обработки — запрос, выбирающий поле
Ссылка. Экономит память. - 🔹 Пройтись по всем видам документов конфигурации — цикл по
Метаданные.Документы. - 🔹 Десятки и сотни тысяч записей — запрос + порционная выборка + фоновое задание.
Перебор через метод Выбрать() менеджера документа
Самый прямой способ обойти документы одного вида — метод Выбрать() у менеджера. Он возвращает объект ВыборкаДокументов, по которому идём циклом Пока ... Цикл.
Выборка = Документы.РеализацияТоваровУслуг.Выбрать();
Пока Выборка.Следующий() Цикл
Сообщить(Выборка.Номер + " от " + Выборка.Дата);
КонецЦикла;
Внутри цикла доступны все реквизиты шапки как поля выборки: Выборка.Дата, Выборка.Номер, Выборка.Контрагент. Это чтение — редактировать данные напрямую здесь нельзя.
Чтобы изменить и записать документ, получите объект:
Выборка = Документы.РеализацияТоваровУслуг.Выбрать();
Пока Выборка.Следующий() Цикл
Объект = Выборка.ПолучитьОбъект();
Объект.Комментарий = "Проверено";
Объект.Записать();
КонецЦикла;
Метод Выбрать() без параметров тянет все документы вида в одном проходе. На таблице в сотни тысяч строк это долгий запрос и риск нехватки памяти. Для больших объёмов используйте запрос с порциями (раздел ниже).
У метода есть параметры отбора и интервала дат:
Отбор = Новый Структура("Проведен", Ложь);
Выборка = Документы.РеализацияТоваровУслуг.Выбрать(
'20250101', '20251231', Отбор);
Так переберём только непроведённые документы за год. Отбор по структуре работает лишь по индексируемым реквизитам; для сложных условий берите запрос.
Обход всех видов документов через метаданные
Если задача — пройтись не по одному виду, а по всем документам конфигурации (например, снять общий отчёт или пересчитать реквизит во всех видах), перебирайте коллекцию Метаданные.Документы.
Для Каждого ОписаниеДок Из Метаданные.Документы Цикл
Запрос = Новый Запрос;
Запрос.Текст = "ВЫБРАТЬ Ссылка ИЗ Документ." + ОписаниеДок.Имя;
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
// обработка Выборка.Ссылка
КонецЦикла;
КонецЦикла;
Здесь ОписаниеДок.Имя подставляется в текст запроса, формируя имя таблицы Документ.ИмяВида. Так код не привязан к конкретным видам и сам подхватит новые документы, добавленные в конфигурацию.
💡 Если нужны не все виды, а по признаку — отфильтруйте по имени или подсистеме. Например, обработать только документы, входящие в определённую подсистему, можно через ОписаниеДок.Подсистемы или проверкой вхождения имени в заранее заданный список.
Полностью универсальный обход всех ссылочных объектов (не только документов) строят через Метаданные.НайтиПоПолномуИмени(), но для документов достаточно коллекции Метаданные.Документы — она явнее и безопаснее.
У каждого описания метаданных доступны и реквизиты состава: ОписаниеДок.Реквизиты, ОписаниеДок.ТабличныеЧасти. Это удобно, когда перебор завязан не на вид документа, а на наличие конкретного реквизита — например, нужно заполнить поле «Ответственный» во всех документах, где оно есть. Тогда перед запросом проверяют ОписаниеДок.Реквизиты.Найти("Ответственный") <> Неопределено и пропускают виды, у которых реквизита нет.
Массовая выборка документов запросом
Для массовой обработки запрос почти всегда лучше метода Выбрать(): вы берёте из базы ровно то, что нужно, и не поднимаете в память реквизиты и табличные части целиком.
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг
|ГДЕ
| Проведен = ИСТИНА
| И Дата МЕЖДУ &Начало И &Конец";
Запрос.УстановитьПараметр("Начало", НачалоГода(ТекущаяДата()));
Запрос.УстановитьПараметр("Конец", КонецГода(ТекущаяДата()));
Выборка = Запрос.Выполнить().Выбрать();
Пока Выборка.Следующий() Цикл
Объект = Выборка.Ссылка.ПолучитьОбъект();
// правки
Объект.Записать();
КонецЦикла;
Важные моменты по языку запросов при переборе:
- Выбирайте только
Ссылка, если дальше всё равно получаете объект. Лишние поля в выборке замедляют запрос. - Все условия пишите в секции
ГДЕчерез параметры (&Имя), а не фильтруйте в цикле на встроенном языке — отбор в запросе уходит на сторону СУБД. - Никогда не склеивайте значения в текст запроса конкатенацией — только через
УстановитьПараметр(). Иначе получите ошибки типов и дыры в выборке.
Запрос возвращает ссылки в произвольном порядке, если не задан УПОРЯДОЧИТЬ ПО. Для порционной обработки порядок обязателен — об этом дальше.
Если по каждому документу нужны ещё и строки табличной части, не получайте объект в цикле ради них — добавьте второй набор данных в тот же запрос. Табличную часть выбирают как Документ.РеализацияТоваровУслуг.Товары с полем Ссылка, а результаты группируют методом Выгрузить() в дерево или связывают по ссылке. Один запрос с соединением почти всегда быстрее, чем тысячи вызовов ПолучитьОбъект() только ради чтения строк.
Порционная обработка больших объёмов
Когда документов десятки и сотни тысяч, нельзя ни держать всю выборку открытой, ни писать всё в одной транзакции. Решение — порции: берём, скажем, по 1000 ссылок, обрабатываем, фиксируем и запрашиваем следующую порцию.
Порции нарезаем по ссылке: выбираем первые N, у которых ссылка больше последней обработанной. Так не нужно хранить смещение и не теряются записи.
ПоследняяСсылка = Документы.РеализацияТоваровУслуг.ПустаяСсылка();
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ ПЕРВЫЕ 1000
| Ссылка
|ИЗ
| Документ.РеализацияТоваровУслуг
|ГДЕ
| Ссылка > &ПоследняяСсылка
|УПОРЯДОЧИТЬ ПО
| Ссылка";
Пока Истина Цикл
Запрос.УстановитьПараметр("ПоследняяСсылка", ПоследняяСсылка);
Результат = Запрос.Выполнить();
Если Результат.Пустой() Тогда
Прервать;
КонецЕсли;
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Объект = Выборка.Ссылка.ПолучитьОбъект();
// правки
Объект.Записать();
ПоследняяСсылка = Выборка.Ссылка;
КонецЦикла;
КонецЦикла;
Такой перебор всех документов не зависит от размера таблицы по памяти: в каждый момент в работе одна порция. Размер порции подбирают опытно — 500–1000 для документов с проведением, до 5000 для простой правки реквизита.
Вынос тяжёлой обработки в фоновое задание
Обработка на минуты и часы не должна висеть в сеансе пользователя: клиент «подвиснет», а по тайм-ауту соединение оборвётся. Длительный перебор выносят в фоновое задание — отдельный серверный процесс.
Параметры = Новый Массив;
Параметры.Добавить(НачалоГода(ТекущаяДата()));
ФоновыеЗадания.Выполнить(
"ОбработкаДокументов.ПеребратьРеализации",
Параметры,
,
"Перепроведение реализаций");
Вызываемая процедура должна лежать в общем модуле с флагом «Сервер» и быть экспортной. Внутри неё — тот самый порционный перебор из предыдущего раздела.
💡 В типовых конфигурациях на БСП есть подсистема «Длительные операции»: она сама запускает фоновое задание, показывает пользователю прогресс-бар и позволяет прервать процесс. Если пишете обработку для типовой базы — используйте её методы вместо «голого» ФоновыеЗадания, не изобретайте индикатор прогресса заново.
Фоновое задание не имеет интерактивного контекста: в нём не работают Предупреждение(), формы и вопросы пользователю. Результат и ошибки передавайте через регистр сведений, журнал регистрации или возвращаемое значение, а не через сообщения на экран.
Транзакции и блокировки при массовой записи
Каждый Объект.Записать() сам по себе — это транзакция плюс блокировки затронутых таблиц и регистров. При переборе всех документов это главная причина взаимоблокировок и жалоб «база висит».
Базовые правила, чтобы массовая запись не мешала работе пользователей:
- Не оборачивайте весь цикл в одну транзакцию. Одна транзакция на сотни тысяч записей держит блокировки до самого конца и копит огромный журнал. Фиксируйте порциями.
- Группируйте запись пачками через
НачатьТранзакцию()/ЗафиксироватьТранзакцию()— по одной транзакции на порцию, а не на документ, и не на всё сразу. - Обязательно закрывайте транзакцию в
Попытка, иначе при ошибке останется незавершённая транзакция.
НачатьТранзакцию();
Попытка
Пока Выборка.Следующий() Цикл
Объект = Выборка.Ссылка.ПолучитьОбъект();
Объект.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Перебор документов",
УровеньЖурналаРегистрации.Ошибка, , ,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение;
КонецПопытки;
Если нужно гарантировать неизменность данных на время обработки — ставьте управляемую блокировку через объект БлокировкаДанных на конкретные таблицы, а не блокируйте всё подряд. Чем уже область блокировки, тем меньше конфликтов с другими сеансами.
Когда документы перепроводить не нужно, а требуется только изменить реквизит, запись можно ускорить, отключив проведение и часть проверок:
Объект.ОбменДанными.Загрузка = Истина;
Объект.Записать();
Флаг ОбменДанными.Загрузка отключает проверку заполнения и движения по регистрам — применяйте его, только когда уверены, что бизнес-логика проведения в этой задаче не нужна, иначе получите расхождение документов и регистров.
Замер производительности и отладка перебора
Прежде чем оптимизировать перебор, измерьте, что именно тормозит — чаще это не цикл, а запись и проведение внутри него.
Простой замер времени участка — через миллисекунды:
Старт = ТекущаяУниверсальнаяДатаВМиллисекундах();
// перебор
Длительность = ТекущаяУниверсальнаяДатаВМиллисекундах() - Старт;
Сообщить("Обработано за " + Длительность / 1000 + " сек");
Инструменты для поиска узких мест:
| Инструмент | Что показывает | Когда применять |
|---|---|---|
| Замер производительности в Конфигураторе | Время по строкам кода | Нашли медленный участок в своей обработке |
| Технологический журнал | События СУБД, тексты запросов, блокировки | Взаимоблокировки, долгие запросы на сервере |
| Центр управления производительностью (КИП/БСП) | Апдекс, долгие операции | Регулярный мониторинг на рабочей базе |
Типичные ошибки, из-за которых перебор документов идёт медленно:
- ❌ Получение объекта там, где хватило бы данных из выборки запроса (
ПолучитьОбъект()в цикле, когда менять ничего не надо). - ❌ Отбор не в запросе, а условием
Есливнутри цикла — база отдаёт лишние строки. - ❌ Запись каждого документа в своей транзакции при больших объёмах вместо пачек.
- ❌ Открытая выборка всей таблицы вместо порций — упирается в память.
При отладке самого цикла ставьте точку останова в отладчике на строке Объект.Записать() и смотрите, на каком документе падает. Для фоновых заданий включите в настройках отладки конфигуратора флаг отладки серверных вызовов и фоновых заданий — иначе точка останова в фоне не сработает.
Частые вопросы
Как перебрать документы только одного вида, но за конкретный период?
ГДЕ Дата МЕЖДУ &Начало И &Конец и параметрами, либо метод Выбрать(Начало, Конец, Отбор) с датами интервала. Запрос предпочтительнее — отбор уходит на сторону СУБД.Чем `ПолучитьОбъект()` отличается от работы с выборкой напрямую?
Выборка.ПолучитьОбъект() или Ссылка.ПолучитьОбъект(). Получение объекта дороже, поэтому вызывайте его, лишь когда действительно пишете.Почему массовая обработка блокирует работу пользователей?
Как обработать документы всех видов, не перечисляя их вручную?
Метаданные.Документы: для каждого элемента берёте Имя и подставляете в текст запроса Документ.ИмяВида. Код сам подхватит новые виды документов, добавленные в конфигурацию.Перебор падает по тайм-ауту — что делать?
ФоновыеЗадания.Выполнить или подсистема «Длительные операции» БСП) и обрабатывайте порциями, сохраняя ссылку последнего обработанного документа, чтобы продолжить после обрыва.