Что происходит при перезаписи элемента справочника и как эффективно хранить остатки товаров

Программист 1С v8.3 (Обычные формы) 1С:Розница Управленческий учет Торговля и дистрибуция
← На главную

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

Механизм записи объекта в базу данных: мифы и реальность

Многие начинающие разработчики полагают, что платформа 1С перед записью объекта сравнивает новые значения реквизитов со старыми и обновляет в СУБД только измененные поля. Это заблуждение. Проанализируем, как на самом деле работает метод Записать() на уровне СУБД:

  1. При вызове метода Записать() платформа формирует SQL-запрос UPDATE. В этот запрос включаются все без исключения колонки основной таблицы справочника, независимо от того, меняли вы их или нет.
  2. СУБД выполняет обновление строки. Даже если значения полей идентичны старым, физическая запись в страницу данных и в журнал транзакций будет произведена.
  3. Обновляются все индексы таблицы. У справочника Номенклатура их может быть десятки: по коду, по наименованию, по владельцу, по артикулу и любым другим индексированным реквизитам. Каждая перезапись объекта заставляет СУБД пересчитывать эти индексы, что создает колоссальную нагрузку на дисковую подсистему.

Рассмотрим ситуацию, когда регламентное задание каждые 5 минут обновляет остатки. Если в справочнике 100 000 позиций, и вы перезаписываете каждую, система выполняет 100 000 тяжелых операций обновления, даже если остаток изменился всего у пары сотен товаров. Это и есть главная причина "тормозов".

Почему запись справочника идет дольше, чем запись регистра

Выясним причину, почему запись в регистр сведений обычно происходит быстрее, чем в справочник:

Во-первых, это ширина таблицы. Таблица справочника Номенклатура крайне "раздута". В ней хранятся текстовые описания, ссылки на единицы измерения, группы, производители и множество служебных полей. Регистр сведений (РС), предназначенный для остатков, содержит всего 2–3 измерения (номенклатура, склад) и 1–2 ресурса. Объем передаваемых данных и объем логов транзакций при записи РС в разы меньше.

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

Оптимизация текущего механизма через ОбменДанными.Загрузка

Если архитектурное решение с хранением в справочнике нельзя изменить быстро, проанализируем способ ускорения записи. Одним из самых эффективных методов является использование свойства ОбменДанными.Загрузка.

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

  1. Игнорируется большинство проверок в подписках на события и модулях объекта.
  2. Не выполняется автоматическая регистрация изменений в планах обмена (если это не переопределено программно).
  3. Запись происходит существенно быстрее, так как платформа минимизирует "обвязку" вокруг SQL-запроса.

Посмотрим на пример кода, как правильно выполнять обновление остатков в справочнике:


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

Запрос.УстановитьПараметр("Период", ТекущаяДата());
Выборка = Запрос.Выполнить().Выбрать();

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

Обратите внимание: в запросе мы добавили условие ГДЕ Остатки.КоличествоОстаток <> Остатки.Номенклатура.ТекущийОстатокРеквизит. Это критически важный момент — мы будем перезаписывать только те элементы, у которых остаток действительно изменился. Это снизит нагрузку на БД в десятки раз.

Переход на регистры сведений: стоит ли игра свеч?

Рассмотрим альтернативу — создание отдельного непериодического регистра сведений для хранения остатков. Многие опасаются, что чтение через ЛЕВОЕ СОЕДИНЕНИЕ будет медленнее, чем прямое обращение к реквизиту справочника через точку. Проанализируем это.

На самом деле, когда вы обращаетесь к реквизиту через точку в запросе (например, Номенклатура.ТекущийОстаток), платформа 1С на уровне SQL все равно выполняет LEFT JOIN с таблицей справочника. Если вы сделаете явное соединение с небольшим регистром сведений по индексированному полю Номенклатура, скорость будет практически идентичной. Более того, работа с узкой таблицей регистра часто оказывается быстрее, так как она лучше кэшируется в оперативной памяти SQL-сервера.

Преимущества использования Регистра Сведений:

Архитектурный подход "Магазин запчастей"

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

Проанализируем алгоритм:

  1. Создаем подписку на события ПриЗаписи для всех документов, которые двигают остатки (Поступление, Реализация, Перемещение).
  2. В подписке определяем список номенклатуры, которая была затронута документом.
  3. Записываем этот список в специальный "буферный" регистр сведений (очередь обновлений).
  4. Регламентное задание раз в минуту берет данные только из этой очереди, рассчитывает реальные остатки по этим позициям и обновляет "быструю таблицу" (реквизиты справочника или основной РС остатков).

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

Выводы и рекомендации

Подведем итог нашему анализу. Если вы столкнулись с медленной записью справочника при обновлении остатков:

  1. Проверьте запросы: Никогда не перезаписывайте все объекты подряд. Сравнивайте текущее значение в справочнике с актуальным остатком и делайте ПолучитьОбъект().Записать() только при наличии расхождений.
  2. Используйте ОбменДанными.Загрузка = Истина: Это отключит тяжелые проверки и ускорит процесс записи в несколько раз.
  3. Рассмотрите переход на Регистр Сведений: Это более правильный с точки зрения архитектуры 1С способ хранения агрегированных данных. Он избавит вас от блокировок основной таблицы справочника.
  4. Оцените необходимость: В современных конфигурациях на управляемых формах динамические списки позволяют выводить остатки через левое соединение с виртуальными таблицами остатков регистра накопления в реальном времени без существенной потери производительности, если настроено верное индексирование.

Соблюдение этих правил позволит сделать вашу систему быстрой и отзывчивой даже при больших объемах данных и интенсивном товарообороте.

← На главную