Как правильно добавлять записи в регистр сведений без измерений и как проектировать логи в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

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

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

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

Для понимания этого процесса заглянем «под капот» платформы «1С:Предприятие» и СУБД (например, MS SQL Server или PostgreSQL):

  1. Ключ уникальности таблицы: В терминах реляционных баз данных измерения регистра сведений составляют его первичный ключ (Primary Key). Если в регистре нет измерений, то ключ уникальности фактически пуст. Это означает, что на уровне СУБД таблица может содержать ровно одну строку. Любая последующая запись в такой регистр будет физически обновлять (делать UPDATE) эту единственную строку.
  2. Аналог константы: Непериодический регистр сведений без измерений превращается в аналог константы. Разница лишь в том, что в регистре мы можем сгруппировать несколько разнородных ресурсов, тогда как классическая константа хранит только одно значение.
  3. Суррогатные ключи (SurrogateKey): Начиная с версии платформы 8.3.7, для поддержки работы через OData платформа создает виртуальный суррогатный ключ для таких таблиц, однако это не меняет логику СУБД — строка в базе по-прежнему может быть только одна.

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

Решение 1. Добавление уникального измерения (УИД / UUID)

Первое решение, которое часто приходит на ум (и предлагается в сообщениях 8 и 9) — добавить в регистр сведений одно-единственное измерение и поместить туда уникальный идентификатор строки (UUID). Например, тип данных Строка (длина 36) или специальный тип УникальныйИдентификатор.

Давайте рассмотрим, как программно выполнялась бы запись в такой регистр сведений:


НоваяЗапись = РегистрыСведений.ЛогСообщений.СоздатьМенеджерЗаписи();
НоваяЗапись.Идентификатор = Новый УникальныйИдентификатор();
НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.ТекстСообщения = "Документ успешно обработан";
НоваяЗапись.Записать();

На первый взгляд, это решает задачу. Каждая запись имеет свой уникальный ключ, и мы можем добавлять неограниченное количество строк. Однако у этого подхода есть скрытая архитектурная проблема, которую называют «бомбой замедленного действия».

Проблема фрагментации индексов: На уровне СУБД по измерениям регистра строится кластерный индекс. Функция Новый УникальныйИдентификатор() генерирует случайные (непоследовательные) значения. При постоянной вставке записей со случайными идентификаторами СУБД вынуждена вставлять строки в случайные места таблицы. Это приводит к массовому расщеплению страниц данных (Page Splits) в SQL-базе. Индексы фрагментируются до 90-99% за считанные дни, что вызывает резкое падение скорости чтения и записи во всей системе.

Решение 2. Периодический регистр сведений с подчинением регистратору

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

В этом случае структура регистра выглядит следующим образом:

Платформа сама будет контролировать уникальность в разрезе каждого документа (регистратора) и момента времени. При проведении документа записи будут автоматически формироваться и перезаписываться, что исключает проблему «мусора» в базе данных при отмене проведения документов.

Решение 3. Использование составного ключа уникальности

Как совершенно верно заметили участники обсуждения в сообщениях 5 и 6, данные при обработке табличной части документа могут полностью дублироваться (один и тот же пользователь, склад, номенклатура и даже время). Чтобы избежать перезаписи строк, нам нужно искусственно гарантировать уникальность ключа.

Рассмотрим оптимальную структуру измерений и ресурсов для непериодического регистра сведений:

Измерения:

  1. Документ (Тип: ДокументСсылка) — связывает запись с конкретным событием.
  2. НомерСтрокиЛога (Тип: Число) — автоинкрементное число, гарантирующее уникальность строк в пределах одного документа.

Ресурсы / Реквизиты:

Проанализируем пример кода для пакетной записи лога по документу:


НаборЗаписей = РегистрыСведений.ЛогСообщений.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Документ.Установить(СсылкаНаДокумент);

// Считываем существующие записи, если нужно дописать новые
НаборЗаписей.Прочитать();

Для Каждого СтрокаТЧ Из СсылкаНаДокумент.Товары Цикл
    НоваяЗапись = НаборЗаписей.Добавить();
    НоваяЗапись.Документ = СсылкаНаДокумент;
    // Формируем уникальный номер строки в пределах набора
    НоваяЗапись.НомерСтрокиЛога = НаборЗаписей.Количество(); 
    НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
    НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
    НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
    НоваяЗапись.Количество = СтрокаТЧ.Количество;
    НоваяЗапись.ТекстСообщения = "Товар готов к отгрузке";
КонецЦикла;

НаборЗаписей.Записать(Истина); // Замещаем старые записи по этому документу

Решение 4. Использование Справочника вместо Регистра сведений

В сообщениях 10, 11 и 12 коллеги затронули фундаментальный спор разработчиков 1С: что лучше для хранения логов — справочник или регистр?

Давайте подробно сопоставим эти два объекта для нашей задачи.

Почему справочник может быть предпочтительнее для хранения логов?

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

Почему регистр сведений все же выбирают чаще?

Основным и самым главным преимуществом регистра сведений в задачах логирования является простота очистки данных (сжатия базы). Логи имеют тенденцию разрастаться до колоссальных размеров за несколько месяцев работы системы.

Пакетная запись и транзакции: развенчиваем мифы

Рассмотрим дискуссию из сообщений 17 и 18. Один из участников утверждает, что запись в регистр сведений происходит быстрее, потому что можно записать сразу 10 000 строк через набор записей с параметром Замещать = Ложь. Второй возражает, что СУБД все равно выполняет операции построчно.

Выясним истину, как работает СУБД под капотом 1С:

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


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

Современные возможности платформы 1С:Предприятие (Версия 8.3.25 и выше)

Если вы работаете на современных версиях платформы 1С, вам доступен новый продвинутый функционал записи независимых регистров сведений. В метод Записать() вместо простого флага Замещение (Истина/Ложь) был добавлен параметр РежимЗамещения (системное перечисление ReplacementMode).

Теперь разработчик может гибко управлять процессом записи:

  1. Замещение (Replace): Стандартное поведение, при котором старые записи по указанному отбору заменяются новыми.
  2. Добавление (Append): Новые строки просто добавляются в конец таблицы СУБД без предварительного удаления. Это экономит время и ресурсы сервера.
  3. Слияние (Merge): Долгожданный режим "Upsert". Если запись с такими измерениями уже существует, платформа обновит ее ресурсы. Если записи нет — она создаст новую.
  4. Удаление (Delete): Позволяет быстро удалить из базы данных записи, переданные в наборе.

Использование режима РежимЗамещения.Добавление делает работу с регистрами сведений еще более производительной при решении задач логирования данных.

← На главную