Чёрный пояс 1С. Динамическая выборка Выбрать() и параллельное переименование: почему элемент выпадает из обхода | infolimp.ru

Чёрный пояс 1С. Динамическая выборка Выбрать() и параллельное переименование: почему элемент выпадает из обхода

11 июня 2026 · infolimp.ru

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

Вопрос с собеседования

Обработка последовательно обходит справочник Номенклатура, упорядоченный по наименованию от А до Я, классическим методом Справочники.Номенклатура.Выбрать(). Пока цикл идёт по товарам на букву «В», другой пользователь в параллельном сеансе переименовывает товар «Эскимо» — до которого выборка ещё далеко не дошла — в «Ананас». Что произойдёт с этим элементом в рамках текущего обхода: он попадёт в выборку под новым именем, под старым, будет обработан дважды или не попадёт вовсе?

Вопрос не про внешние компоненты и не про экзотику — он проверяет, понимает ли кандидат разницу между запросом и объектной выборкой. На нём спотыкаются и разработчики с десятилетним стажем.

Где ловушка

Интуиция подсказывает: раз метод Выбрать() возвращает выборку, платформа наверняка открыла транзакцию и зафиксировала для себя «снимок» данных на момент вызова. Или хотя бы считала все ссылки в память сервера. В обеих картинах мира товар будет корректно обработан ровно один раз — максимум под старым именем.

Обе картины неверны.

Правильный ответ

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

Почему так происходит

Методы Выбрать() и ВыбратьИерархически() создают динамическую выборку. Она не считывает таблицу целиком и не строит снимок: платформа забирает записи из СУБД небольшими порциями по мере того, как ваш цикл вызывает Следующий() (в методических разборах обычно называют порцию в 25 записей; точный размер — внутренняя деталь реализации, на которую полагаться не стоит). Очередная порция читается только тогда, когда предыдущая обработана. Между этими чтениями данные живут своей жизнью, и никакого контроля над их актуальностью у вас нет.

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

  1. Пропуск. Наш случай: «Эскимо» переехал из конца алфавита в начало, став «Ананасом». Выборка уже прошла букву «А» и стоит на «В» — переименованный элемент «опоздал» в уже прочитанные порции и не дожил до конца списка. Для текущего обхода его не существует.
  2. Задвоение. Обратная ситуация: если бы «Апельсин» в середине обхода переименовали в «Яблоко», один и тот же элемент попал бы в выборку дважды — сначала в начале алфавита, потом в конце.
Важно: упорядочивание по наименованию здесь только для наглядности. Тот же эффект даёт любое изменение полей, по которым упорядочена выборка, — включая код и реквизиты. Без явного упорядочивания вы просто не знаете, по какому полю СУБД отсортирует записи, и эффект становится ещё менее предсказуемым. И ещё одна граница применимости: всё описанное относится к клиент-серверному режиму, где порции читает СУБД; в файловой базе движок хранения другой — но полагаться там на параллельный обход тем более не стоит.

Как обходить справочник надёжно

Если нужен гарантированный полный обход «каждый элемент ровно один раз» — зафиксируйте состав обрабатываемых элементов сами: выберите ссылки одним запросом и обходите уже его результат. Результат запроса фиксирует состав ссылок в момент выполнения: параллельные переименования не добавят и не уберут элементы из этого списка.

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

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

Нюансы, которые стоит проговорить вслух:

Что спросить себя на проекте

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

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

Профессиональные решения для 1С и marketplace-интеграций — каталог отчётов и инструментов на витрине НОПи.

Перейти в каталог решений →