При интеграции с внешними поставщиками или складами ответственного хранения часто возникает задача регулярной загрузки снимков (снапшотов) товарных остатков. Формат входящих данных обычно прост: дата фиксации остатков, номенклатурная позиция и ее количество. Перед разработчиком встает дилемма проектирования структуры хранения: создать отдельный документ без проведения с табличной частью или использовать регистр сведений. Разберем по шагам архитектурные особенности обоих подходов, проанализируем влияние на производительность базы данных и выясним оптимальное решение.
Рассмотрим, что происходит внутри реляционной базы данных (СУБД) при использовании документа без проведения:
Ссылка), номер строки (НомерСтроки), а также реквизиты самой табличной части. Таким образом, аргумент об экономии места за счет выноса даты в шапку документа не выдерживает критики: ссылка типа УИД (16 байт) и номер строки занимают в каждой записи больше объема, чем стандартное поле типа Дата (8 байт).Ссылка + НомерСтроки. Чтобы получить срез остатков или отфильтровать товар по истории в запросе, придется соединять таблицу документа с его шапкой для получения даты, а поиск по товару приведет к сканированию таблицы (Index Scan / Table Scan), если у реквизита номенклатуры явным образом не включено индексирование.Назначение документа в архитектуре 1С — это фиксация факта хозяйственной операции и формирование движений. Хранить периодические отчетные срезы в строках непроведенного документа является антипаттерном. Для хранения нормализованных срезов и состояний предназначен регистр сведений.
Выясним, как правильно спроектировать регистр сведений под задачу загрузки полных снимков остатков на дату. Здесь есть два основных подхода:
Период в начало кластерного индекса. Если периодичность установлена в пределах дня, платформа гарантирует уникальность только одной записи на товар в течение суток. Однако стандартные виртуальные таблицы среза последних здесь могут работать не так, как ожидается: если товар был на остатке вчера, а сегодня в файле его нет (остаток стал равен нулю), срез последних без явной записи нулевого остатка продолжит возвращать вчерашнее значение.ДатаОстатков (тип Дата, состав «Дата»), Поставщик (при необходимости) и Номенклатура. Ресурсом выступает Количество. В таком регистре виртуальные таблицы срезов отсутствуют, а выборка остатка на нужную дату выполняется обычным прямым запросом с отбором по измерению даты, что гарантирует точное совпадение с загруженным файлом без наложения итогов за прошлые дни.Проанализируем порядок измерений в регистре. В независимом регистре сведений кластерный индекс СУБД выстраивается строго в порядке следования измерений в дереве метаданных конфигуратора. Если первым измерением сделать ДатаОстатков, то поиск по номенклатуре без указания даты потребует сканирования всего индекса. Если наиболее частым сценарием является поиск остатков конкретного товара по всей хронологии, измерение Номенклатура должно предшествовать дате, либо для номенклатуры должно быть выставлено свойство «Индексировать».
При ежедневной загрузке тысяч строк критически важно избегать построчной записи через РегистрСведенийМенеджерЗаписи. Одиночные записи создают избыточную нагрузку на транзакционный журнал СУБД и провоцируют блокировки.
Разберем пример эффективной записи данных через РегистрСведенийНаборЗаписей с предварительной очисткой данных за загружаемую дату:
НаборЗаписей = РегистрыСведений.ОстаткиТоваровПоставщиков.СоздатьНаборЗаписей();
// Устанавливаем жесткий отбор по границам перезаписываемых данных
НаборЗаписей.Отбор.ДатаОстатков.Установить(ТекущаяДатаОстатков);
НаборЗаписей.Отбор.Поставщик.Установить(ТекущийПоставщик);
// Заполняем набор записей из обработанной таблицы входящего XML
Для Каждого СтрДанных Из ТаблицаОстатков Цикл
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.ДатаОстатков = ТекущаяДатаОстатков;
НоваяЗапись.Поставщик = ТекущийПоставщик;
НоваяЗапись.Номенклатура = СтрДанных.Номенклатура;
НоваяЗапись.Количество = СтрДанных.Количество;
КонецЦикла;
// Записываем набор с замещением в рамках явной транзакции
НачатьТранзакцию();
Попытка
НаборЗаписей.Записать(Истина);
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ЗаписьЖурналаРегистрации("Интеграция.Остатки", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
ВызватьИсключение;
КонецПопытки;
Если поставщик присылает десятки тысяч позиций каждый день, плоская запись приведет к лавинообразному росту таблиц базы данных и фрагментации страниц индексов. Рассмотрим, какие методы позволяют оптимизировать хранение:
ХранилищеЗначения служебного справочника или сохраняются на выделенном дисковом томе. Это исключает хранение миллионов тяжелых записей в индексированных реляционных таблицах.