При разработке архитектуры в 1С часто возникает задача спроектировать регистр, который будет хранить большой объем данных, например, лог действий или статусы объектов. Классическая проблема в такой ситуации — как правильно организовать измерения, если одно из них должно ссылаться на документы разных типов (например, ЗаказПокупателя, ЗаказПоставщику и т.д.), особенно если в будущем список типов может расширяться.
Основной вопрос, который волнует разработчика, — производительность. Что лучше: одно измерение с составным типом данных или несколько отдельных измерений для каждого типа документа? Давайте детально разберем этот вопрос и проанализируем различные подходы, их плюсы и минусы.
Сразу отметим, что вариант с созданием 5-7 разных измерений для каждого типа документа — крайне неэффективен и является плохой практикой. Это приведет к "раздуванию" таблицы регистра в СУБД, появлению большого количества полей со значением NULL, что негативно скажется на размере базы и скорости выполнения запросов. Поэтому этот вариант мы рассматривать не будем и сосредоточимся на двух рабочих подходах.
Это стандартный способ, который предлагает платформа 1С и который используется в большинстве типовых конфигураций. Он заключается в создании одного измерения, в настройках которого указываются все необходимые типы документов.
Как это работает на уровне СУБД?
Многие опасаются, что составной тип данных создает нелинейную нагрузку на базу данных. Проанализируем, что происходит "под капотом". Когда вы создаете измерение (или реквизит) с составным типом, платформа 1С в таблице базы данных создает для него два физических поля:
Индекс для этого измерения строится сразу по обоим полям. Современные СУБД (например, MS SQL или PostgreSQL) отлично оптимизированы для работы с такими составными индексами. Да, небольшой оверхед по сравнению с индексом по одному простому полю есть (например, лишние байты на хранение типа), но он, как правило, незначителен и с лихвой компенсируется удобством и простотой разработки.
Преимущества этого подхода:
Недостатки:
Этот подход является наиболее сбалансированным и подходит для подавляющего большинства задач, включая те, где требуется не только добавление, но и поиск, и редактирование записей.
В ситуациях, когда регистр используется как сверхбыстрый лог, куда записи добавляются с очень высокой частотой (сотни записей в секунду), и главной задачей является минимизация блокировок при записи, можно применить более сложный, но эффективный метод.
Структура регистра в этом случае будет следующей:
УникальныйИдентификатор. Это измерение будет обеспечивать уникальность записей и будет ведущим в индексе.Ключевая особенность — способ записи данных:
Главное преимущество этого подхода — возможность использовать метод НаборЗаписей.Записать(Ложь). Разберем, в чем разница.
НаборЗаписей.Записать(<strong>Истина</strong>): Этот режим по умолчанию сначала полностью очищает все записи в регистре, которые соответствуют установленному отбору, а затем вставляет новые. Чтобы в процессе между чтением и записью не появились "фантомные" записи от другой транзакции, 1С накладывает управляемую блокировку на весь диапазон, определяемый отбором. В высоконагруженных системах это частая причина длительных ожиданий и взаимных блокировок.НаборЗаписей.Записать(<strong>Ложь</strong>): Этот режим не удаляет существующие записи, а просто добавляет новые. Он идеально подходит для логов. Поскольку старые данные не затрагиваются, риск появления фантомов на уровне логики 1С отсутствует, и платформа не ставит управляемую блокировку. Уникальность записей гарантируется первичным ключом (индексом) на уровне СУБД. При попытке вставить дубликат СУБД вернет ошибку, которую платформа обработает.Пример кода для записи:
Процедура ЗаписатьВЛог(ДокументСсылка)
НаборЗаписей = РегистрыСведений.МойЛог.СоздатьНаборЗаписей();
// Отбор НЕ устанавливаем, чтобы не вызывать блокировок
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.УникальныйИдентификатор = Новый УникальныйИдентификатор(); // Ключевое поле
НоваяЗапись.ДокументРегистратор = ДокументСсылка; // Ссылка в реквизите
// ... заполняем остальные реквизиты и ресурсы
// Записываем в режиме добавления, без блокировок
НаборЗаписей.Записать(Ложь);
КонецПроцедуры
Особенности индексации и запросов:
Важно понимать, как работают индексы в этой схеме:
УникальныйИдентификатор будет основным и кластерным.ДокументРегистратор будет дополнительным, некластерным.Основной недостаток этого подхода — усложнение получения данных. Чтобы найти все записи по конкретному документу, вам придется делать запрос с условием по реквизиту, а не по измерению, что может быть чуть менее эффективно, чем поиск по кластерному индексу в классической схеме. Однако для задач логирования, где запись важнее чтения, это оправданная цена.
Подведем итог и сформулируем четкие рекомендации по выбору.
Для 95% случаев используйте классический подход: одно измерение с составным типом.
Если ваш регистр — это не просто лог, а полноценная часть бизнес-логики, где записи могут читаться, искаться, редактироваться при проведении документов, то этот вариант будет самым простым, надежным и достаточно производительным. Не стоит преждевременно усложнять архитектуру из-за теоретических опасений по поводу производительности составных типов.
Для сверхнагруженных append-only логов рассмотрите подход с УникальнымИдентификатором.
Если ваша задача — максимально быстро и без блокировок писать огромное количество записей (например, лог интеграций, действий пользователей в реальном времени), а чтение этих данных происходит редко и не требует мгновенного отклика, то второй подход даст значительный выигрыш в производительности записи.
В вашей исходной задаче упоминается не только запись, но и "редактирование через МенеджерЗаписи" и "изменение записей при проведении документов". Это сильный аргумент в пользу первого, классического подхода, так как он гораздо удобнее для операций поиска и модификации существующих записей.