Постраничный обход теряет половину набора и заканчивается без ошибок. Где вторая половина

24.09.26

Разработка - Рефакторинг и качество кода

В отчёте регламентного задания обычно стоит одно число: сколько записей обработано. Этого числа недостаточно, чтобы заметить целый класс дефектов, и вот один из них. Если вы обходите выборку постранично, двигаете смещение и в том же цикле снимаете признак, по которому идёт отбор, набор сжимается под ногами, и часть записей остаётся позади. Обработается около половины, и ровно половина в типичном случае, когда страница много меньше набора. Ошибок при этом не будет ни одной, счётчик сойдётся, цикл выйдет по своему условию. Показываю механику на двадцати записях, даю модель, чтобы вы проверили доли на своих размерах, разбираю зеркальный случай с дублями при растущем наборе и то единственное число, которое обязано стоять в отчёте рядом с первым.

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

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

 

Как это выглядит в коде

Смещение = 0;
Размер = 100;

Пока Истина Цикл

	// отбор: "ещё не обработано"
	Порция = ПолучитьНеобработанные(Смещение, Размер);

	Если Порция.Количество() = 0 Тогда
		Прервать;
	КонецЕсли;

	Для Каждого Запись Из Порция Цикл
		Обработать(Запись);        // здесь признак "не обработано" снимается
	КонецЦикла;

	Смещение = Смещение + Размер;

КонецЦикла;

Сразу оговорю, откуда в 1С берётся смещение, потому что в языке запросов его нет: есть ПЕРВЫЕ и нет OFFSET. Смещение появляется там, где порцию отдаёт что-то помимо запроса: внешний сервис с параметрами страницы, свой HTTP-метод, промежуточный слой над выборкой. У меня это был внешний сервис. Если же вы один раз выбрали все ключи в таблицу значений и режете её в памяти, описанного дефекта у вас нет: набор зафиксирован на старте и больше не меняется.

Проблема начинается ровно тогда, когда набор пересобирается на каждой странице. Он сжимается между страницами на ту же величину, на которую растёт смещение.

 

Почему пропадает половина

Разберу на двадцати записях, страница пять.

Первая страница. Смещение ноль, набор из двадцати. Берём записи с первой по пятую, обрабатываем, признак снят. В наборе остаётся пятнадцать: с шестой по двадцатую.

Вторая страница. Смещение пять. Набор теперь начинается с шестой записи, поэтому смещение в пять позиций пропускает записи с шестой по десятую, и порция начинается с одиннадцатой. Берём с одиннадцатой по пятнадцатую.

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

Третья страница. Смещение десять, а в наборе осталось десять записей. Срез с десятой позиции пуст, и цикл решает, что дошёл до конца.

Вот это и есть самое важное место: цикл вышел не потому, что записи кончились, а потому, что смещение обогнало набор. Условие выхода выглядит правильным и врёт.

Итог: из двадцати обработано десять. Пропущены записи с шестой по десятую и с шестнадцатой по двадцатую.

 

Половина это нижняя граница, а дальше зависит от соотношения

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

Вот честная картина, посчитанная моделью:

Набор Страница Набор к странице Обработано Доля
1 000 10 100 500 50,0 %
1 000 100 10 500 50,0 %
1 000 150 6,7 550 55,0 %
1 000 200 5 600 60,0 %
1 000 333 3 666 66,6 %
10 000 100 100 5 000 50,0 %

В колонке доля стоит обработанное, то есть потеря это остальное.

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

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

 

Как это выглядело у меня

Честно про свой случай, потому что тут я чуть не написал лишнего.

Прогон ходил во внешний сервис за данными по номерам документов. Набор задавался отбором "данных ещё нет", запись, получившая данные, из отбора выпадала, а страницы брались по смещению. То есть конструкция ровно та, что разобрана выше.

Грабля была замечена и записана в протокол прогона в тот же день: обход по сжимающемуся набору пропускает середину, лечится повторными прогонами до нуля. А вот сколько именно он тогда потерял, я не мерил. Прогон переделали, и вопрос закрылся.

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

 

Почему отчёт остаётся зелёным

Вот что делает дефект дорогим.

Ошибки нет ни одной, и её действительно нет: каждая обработанная запись обработана правильно. Счётчик обработанных честен. Цикл завершился по своему условию, без таймаута. Журнал чист.

Больше того, промежуточные сверки сходятся. Посчитаете после прогона, сколько записей сняли признак, получите ровно столько, сколько отправляли. Сверка отправленного с обработанным не поймает ничего, потому что она про другое.

Единственное число, которое это ловит, это остаток. Сколько записей осталось в наборе после прогона, который по замыслу должен был набор опустошить.

 

Как поймать заранее

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

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

 

Как проверить за минуту, не дожидаясь ночи

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

Напишите прогон, который ничего не делает с данными, а только считает:

  1. Посчитайте размер набора по отбору. Это число до.
  2. Пройдите обход своим кодом, но вместо обработки складывайте ключи в соответствие.
  3. Посчитайте размер набора снова. Это число после.
  4. Сравните: собранных ключей должно быть столько же, сколько разница между до и после, а число после должно быть нулём.

Если в коде та самая ошибка, разница сойдётся, а ноль не получится. Приём шире одного дефекта: он работает везде, где обход меняет то, по чему идёт.

 

Три способа починить, и правильный один

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

Второй, рабочий, но дорогой: гонять до нуля. Прогон со смещением можно просто повторять, пока остаток не станет нулевым: каждый заход забирает примерно половину остатка, а хвост добивается быстрее, потому что последние прогоны укладываются в одну-две страницы. Для набора в тысячу при странице в сто это четыре прогона, для десяти тысяч при той же странице семь. Способ честный и годится, когда чужой код править нельзя.

Третий, правильный: не двигать смещение вообще.

Размер = 100;

Пока Истина Цикл

	// всегда с начала набора: обработанные из него уже вышли
	Порция = ПолучитьНеобработанные(0, Размер);

	Если Порция.Количество() = 0 Тогда
		Прервать;
	КонецЕсли;

	Для Каждого Запись Из Порция Цикл
		Обработать(Запись);
	КонецЦикла;

КонецЦикла;

Смещение не нужно вовсе. Набор сам двигается вам навстречу: обработанное из него выходит, и следующая порция снова лежит в начале. Условие выхода становится честным, потому что пустая порция теперь действительно означает пустой набор.

У этого способа есть своя цена, и её надо назвать сразу. Если запись не смогла обработаться и признак с неё не снялся, она вернётся в следующей порции, и цикл станет вечным. Нужен признак неудачной попытки или счётчик попыток, иначе одна битая запись остановит всю обработку. В варианте со смещением этой беды нет: там битая запись просто остаётся позади, что, собственно, и есть исходный дефект, только повёрнутый другой стороной.

 

Порядок без явной сортировки добивает картину

Страница в запросе обычно берётся через ПЕРВЫЕ:

Запрос.Текст =
	"ВЫБРАТЬ ПЕРВЫЕ 100
	|	Ном.Ссылка КАК Ссылка
	|ИЗ
	|	Справочник.Номенклатура КАК Ном
	|ГДЕ
	|	НЕ Ном.ДанныеПолучены";

Здесь нет УПОРЯДОЧИТЬ ПО, и синтаксически это законно. Но без явного порядка платформа не обязана возвращать строки в одном и том же порядке между вызовами: порядок отдаёт СУБД, и от вызова к вызову он может отличаться. Утверждать, отчего именно он меняется, я не берусь: механизм я не проверял, а проверять его надо на своей связке платформы и СУБД.

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

Поэтому в постраничном обходе сортировка стоит явно и по полю, которое не меняется по ходу обработки. Ссылка подходит, дата или статус нет: они как раз и меняются.

Наблюдение снято на платформе 8.3.27 с MS SQL. На вашей связке порядок может вести себя иначе, и это ещё одна причина ставить сортировку руками.

 

Зеркальный случай: набор растёт, и вы обрабатываете одно и то же

Пропуск это половина истории. Вторая половина случается, когда набор во время обхода растёт.

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

Итог противоположный по форме и такой же по природе: часть записей уезжает наружу дважды, и ошибок снова нет. Получатель может ответить успехом на оба раза, и тогда про дубль вам скажет он. Ваш журнал промолчит.

Лечится тем же: смещение убирается, обход идёт по началу набора. Плюс у отправки должен быть ключ, по которому получатель узнаёт повтор и не создаёт вторую сущность.

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

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

 

Чем я это делал

Модель, по которой посчитана таблица, умещается в полтора десятка строк. Привожу её целиком, чтобы вы подставили свой набор со своей страницей и не верили мне на слово.

Функция ОбработаноЗаПрогон(Всего, Страница)

	Набор = Новый Массив;
	Для Н = 1 По Всего Цикл
		Набор.Добавить(Н);
	КонецЦикла;

	Смещение = 0;
	Пока Смещение < Набор.Количество() Цикл
		// порция это срез набора начиная со смещения
		Конец = Мин(Смещение + Страница, Набор.Количество());
		Если Конец <= Смещение Тогда
			Прервать;
		КонецЕсли;
		// обработанные выходят из набора: удаляем с конца, чтобы не сбить индексы
		Инд = Конец - 1;
		Пока Инд >= Смещение Цикл
			Набор.Удалить(Инд);
			Инд = Инд - 1;
		КонецЦикла;
		Смещение = Смещение + Страница;
	КонецЦикла;

	Возврат Всего - Набор.Количество();

КонецФункции

Прогоните её на наборе 20 со страницей 4 и увидите шестьдесят процентов вместо пятидесяти. Это и есть та граница, о которой раздел выше.

Из выложенного по теме диагностики регламентных заданий:

 

Другие наши инструменты

 

Вопрос

Открытый вопрос у меня про третий способ, тот, где смещение не двигают.

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

Как вы это решаете? Отдельный регистр с числом попыток, реквизит на записи, ограничение по числу итераций с разбором остатка глазами? И сталкивался ли кто-нибудь с тем, что счётчик попыток сам стал источником проблем: запись починилась, а счётчик остался и держит её вне обработки.

Вступайте в нашу телеграмм-группу Инфостарт

постраничный обход 1С пагинация смещение регламентное задание пропускает записи выборка по признаку ПЕРВЫЕ в запросе 1С обход очереди 1С дубли при выгрузке УПОРЯДОЧИТЬ ПО в постраничном запросе

Вы можете заказать платную адаптацию этой статьи под ваши задачи на «Бирже заказов».

  • 0% комиссии — оплата напрямую исполнителю;
  • Исполнители любого масштаба — от отдельных специалистов до команд под проект;
  • Прямой обмен контактами между заказчиком и исполнителем;
  • Безопасная сделка — при необходимости;
  • Рейтинги, кейсы и прозрачная система откликов.

См. также

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Комплексная автоматизация 1.х 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Платные (руб)

Перенос данных из 1С:Управление производственным предприятием 1.3 в 1С:Бухгалтерия предприятия 3.0 с помощью правил обмена | Можно выполнить переход с УПП на БП 3 или запускать выгрузку данных за выбранный период времени | Переносятся документы, начальные остатки и вся справочная информация | Есть фильтр по организации и множество других параметров выгрузки | Поддерживается несколько сценариев работы: как первичный полный перенос, так и перенос только новых документов | Перенос данных возможен в "1С: Бухгалтерия 3.0" версии ПРОФ, КОРП или базовую | Переход с "1С: УПП1.3" / "1С:КА 1.1" на "1С:БП3.0" с помощью правил конвертации будет максимально комфортным! | Можно бесплатно проверить перенос на вашем сервере!

50050 руб.

25.02.2015    191536    376    295    

431

Перенос данных 1C Разработчик 1С:Предприятие 8 1С:Управление производственным предприятием 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Платные (руб)

Перенос документов, начальных остатков и справочной информации из УПП 1.3 в ERP 2 | из УПП 1.3 в УТ 11 | из УПП в КА 2 | Правила конвертации (КД 2) | Более 360 предприятий выполнили переход с использованием этого продукта! | Сэкономьте время - используйте готовое решение для перехода! | Позволяет перенести из УПП 1.3 в ERP / УТ 11 / КА 2 всю возможную информацию | В переносе есть фильтр по организации и множество других опциональных параметров выгрузки | Есть несколько алгоритмов выгрузки остатков на выбор

58000 руб.

04.08.2015    193439    468    309    

466

SALE! 15%

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Розница 2 1С:Управление нашей фирмой 1.6 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х 1С:Управление нашей фирмой 3.0 1С:Розница 3.0 Россия Платные (руб)

Правила в универсальном формате обмена для ERP 2.5, КА 2.5, УТ 11.5, БП 3.0, Розница, УНФ, для последних версий конфигураций. Ссылки на другие конфигурации в описании публикации. Правила совместимы со всеми другими версиями конфигураций новыми и старыми, поддерживающими обмен и синхронизацию в формате EnterpriseData. Не требуется синхронного обновления правил после обновления другой конфигурации, участвующей в обмене. Типовой обмен через планы обмена кнопкой Синхронизация вручную или автоматически по расписанию, или вручную обработкой.

27633 руб.

12.06.2017    163956    996    329    

488

Перенос данных 1C Взаиморасчеты Оптовая торговля Логистика, склад и ТМЦ Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Управление торговлей 10 1С:ERP Управление предприятием 2 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Управленческий учет Платные (руб)

Можно проверить до покупки, оставьте заявку! Воспользовались более 268 компаний! Перенос данных из УТ 10.3 в УТ 11 | из УТ 10.3 в КА 2 | из УТ 10.3 в ERP. Решение для перехода с УТ 10.3. Можно перенести начальные остатки, нормативно-справочную информацию и все возможные документы. При выгрузке можно установить отбор по периоду, организациям и складам.

50200 руб.

24.04.2015    210172    182    253    

300

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Разработчик 1С:Предприятие 8 1С:ERP Управление предприятием 2 1С:Бухгалтерия 3.0 1С:Управление торговлей 11 1С:Комплексная автоматизация 2.х Россия Платные (руб)

Перенос данных из ERP в БП 3 | из КА 2 в БП 3 | из УТ 11 в БП 3 | из ЕРП в БП 3 | Сэкономьте время - используйте готовое решение для перехода! | Перенос разработан в формате КД 2 (правила конвертации данных) | Переносятся все возможные виды документов, начальных остатков и нормативно-справочная информация| Можно опционально выгружать каждую пару "номенклатура+характеристика" как отдельную номенклатуру | Есть выгрузка настроек счетов учета и зарплатных данных из ERP / КА 2 | Можно проверить на вашем сервере перед покупкой

58000 руб.

15.04.2019    86786    233    182    

168

SALE! 10%

Перенос данных 1C Файловый обмен (TXT, XML, DBF), FTP Системный администратор Разработчик 1С:Предприятие 8 1С:Управление производственным предприятием 1С:Бухгалтерия 3.0 Россия Бухгалтерский учет Управленческий учет Платные (руб)

Переносите справочную информацию, остатки и документы из УПП 1.3 в Бухгалтерию 3.0 с помощью готовых правил. Переносится более 50 видов документов. Простой интерфейс и понятные настройки.

42000 37800 руб.

15.12.2021    36280    265    68    

202

Файловый обмен (TXT, XML, DBF), FTP Перенос данных 1C Системный администратор Разработчик Бухгалтер 1С:Предприятие 8 1С:Бухгалтерия 3.0 Россия Платные (руб)

Обработка не только формирует начальные остатки по всем счетам на нужную дату (экономя время на свёртке базы БП 3), но и полностью переносит справочные данные и документы за заданный период. Гибкая настройка включает фильтр по организациям и множество параметров выгрузки. Работайте в удобном формате: выполните однократный полный переход или настройте регулярную догрузку только новых документов из БП 3 в БП 3.0. Интеграция правил конвертации в план обмена гарантирует точную выгрузку исключительно зарегистрированных объектов.

70760 руб.

10.04.2026    1269    3    8    

2

Рабочее место Производство готовой продукции (работ, услуг) Перенос данных 1C Пользователь 1С:Предприятие 8 1С:Управление производственным предприятием 1С:Документооборот 1С:Комплексная автоматизация 2.х 1С:КА 1С:ДО Платные (руб)

Продукт "Интеграция с 1С:Документооборот" позволяет использовать функции программы "1С:Документооборот 8" напрямую из учетной системы (1С:УПП; 1С:КА, 1С:УТ 10.3, 1С:БГУ 1.0, 1С:ЗБУ 1.0, 1С:УПП для Казахстана и отраслевых решений, разработанных на их основе) на платформе "1С:Предприятие 8": выполнять и ставить задачи, просматривать документы, скан-копии и прочие файлы, штрих-кодировать документы отправлять письма, вести учет рабочего времени - не входя в "1С:Документооборот 8", работая в одной программе, что значительно сокращает время и делает работу более комфортной и эффективной. Продукт прошел сертификацию 1С-Совместимо

135530 руб.

11.06.2015    63641    39    20    

51
Для отправки сообщения требуется регистрация/авторизация