При проектировании систем логирования или хранения истории сообщений разработчики часто сталкиваются с архитектурным вопросом: как правильно организовать хранение данных в 1С? В этой статье мы подробно разберем проблему, с которой столкнулся автор темы, выясним внутреннее устройство регистров сведений на уровне СУБД, а также рассмотрим альтернативные и наиболее производительные подходы к решению этой задачи.
Давайте проанализируем ситуацию: автор создал независимый непериодический регистр сведений и добавил в него только ресурсы, оставив раздел измерений пустым. При попытке записать вторую строку система ожидаемо перезаписывает первую или выдает ошибку уникальности. Почему так происходит?
Для понимания этого процесса заглянем «под капот» платформы «1С:Предприятие» и СУБД (например, MS SQL Server или PostgreSQL):
Таким образом, хранить лог сообщений, историю действий пользователей или перечень отгрузок в регистре без измерений физически невозможно. Нам необходимо обеспечить уникальность каждой записи.
Первое решение, которое часто приходит на ум (и предлагается в сообщениях 8 и 9) — добавить в регистр сведений одно-единственное измерение и поместить туда уникальный идентификатор строки (UUID). Например, тип данных Строка (длина 36) или специальный тип УникальныйИдентификатор.
Давайте рассмотрим, как программно выполнялась бы запись в такой регистр сведений:
НоваяЗапись = РегистрыСведений.ЛогСообщений.СоздатьМенеджерЗаписи();
НоваяЗапись.Идентификатор = Новый УникальныйИдентификатор();
НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.ТекстСообщения = "Документ успешно обработан";
НоваяЗапись.Записать();
На первый взгляд, это решает задачу. Каждая запись имеет свой уникальный ключ, и мы можем добавлять неограниченное количество строк. Однако у этого подхода есть скрытая архитектурная проблема, которую называют «бомбой замедленного действия».
Проблема фрагментации индексов: На уровне СУБД по измерениям регистра строится кластерный индекс. Функция Новый УникальныйИдентификатор() генерирует случайные (непоследовательные) значения. При постоянной вставке записей со случайными идентификаторами СУБД вынуждена вставлять строки в случайные места таблицы. Это приводит к массовому расщеплению страниц данных (Page Splits) в SQL-базе. Индексы фрагментируются до 90-99% за считанные дни, что вызывает резкое падение скорости чтения и записи во всей системе.
Если данные, которые вы хотите записывать, напрямую связаны с документами (например, статус обработки табличной части документа), отличным решением будет сделать регистр сведений подчиненным регистратору.
В этом случае структура регистра выглядит следующим образом:
Платформа сама будет контролировать уникальность в разрезе каждого документа (регистратора) и момента времени. При проведении документа записи будут автоматически формироваться и перезаписываться, что исключает проблему «мусора» в базе данных при отмене проведения документов.
Как совершенно верно заметили участники обсуждения в сообщениях 5 и 6, данные при обработке табличной части документа могут полностью дублироваться (один и тот же пользователь, склад, номенклатура и даже время). Чтобы избежать перезаписи строк, нам нужно искусственно гарантировать уникальность ключа.
Рассмотрим оптимальную структуру измерений и ресурсов для непериодического регистра сведений:
Измерения:
Документ (Тип: ДокументСсылка) — связывает запись с конкретным событием.НомерСтрокиЛога (Тип: Число) — автоинкрементное число, гарантирующее уникальность строк в пределах одного документа.Ресурсы / Реквизиты:
ДатаВремя (Тип: Дата)Пользователь (Тип: СправочникСсылка.Пользователи)Номенклатура (Тип: СправочникСсылка.Номенклатура)Количество (Тип: Число)ТекстСообщения (Тип: Строка)Проанализируем пример кода для пакетной записи лога по документу:
НаборЗаписей = РегистрыСведений.ЛогСообщений.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Документ.Установить(СсылкаНаДокумент);
// Считываем существующие записи, если нужно дописать новые
НаборЗаписей.Прочитать();
Для Каждого СтрокаТЧ Из СсылкаНаДокумент.Товары Цикл
НоваяЗапись = НаборЗаписей.Добавить();
НоваяЗапись.Документ = СсылкаНаДокумент;
// Формируем уникальный номер строки в пределах набора
НоваяЗапись.НомерСтрокиЛога = НаборЗаписей.Количество();
НоваяЗапись.ДатаВремя = ТекущаяДатаСеанса();
НоваяЗапись.Пользователь = ПараметрыСеанса.ТекущийПользователь;
НоваяЗапись.Номенклатура = СтрокаТЧ.Номенклатура;
НоваяЗапись.Количество = СтрокаТЧ.Количество;
НоваяЗапись.ТекстСообщения = "Товар готов к отгрузке";
КонецЦикла;
НаборЗаписей.Записать(Истина); // Замещаем старые записи по этому документу
В сообщениях 10, 11 и 12 коллеги затронули фундаментальный спор разработчиков 1С: что лучше для хранения логов — справочник или регистр?
Давайте подробно сопоставим эти два объекта для нашей задачи.
Основным и самым главным преимуществом регистра сведений в задачах логирования является простота очистки данных (сжатия базы). Логи имеют тенденцию разрастаться до колоссальных размеров за несколько месяцев работы системы.
Рассмотрим дискуссию из сообщений 17 и 18. Один из участников утверждает, что запись в регистр сведений происходит быстрее, потому что можно записать сразу 10 000 строк через набор записей с параметром Замещать = Ложь. Второй возражает, что СУБД все равно выполняет операции построчно.
Выясним истину, как работает СУБД под капотом 1С:
INSERT для каждой строки (технология массовой вставки bulk-insert стандартно не применяется).Записать(), то для каждого элемента платформа открывает, выполняет и фиксирует (Commit) отдельную транзакцию в СУБД. Это создает огромную нагрузку на диск из-за записи в журнал транзакций СУБД.Чтобы ускорить запись справочника до скорости регистра сведений, достаточно явно открыть транзакцию перед циклом записи элементов:
НачатьТранзакцию();
Попытка
Для Каждого СтрокаТЧ Из СписокДокументов Цикл
НовыйЛог = Справочники.ЛогСообщений.СоздатьЭлемент();
НовыйЛог.Документ = СтрокаТЧ.Ссылка;
НовыйЛог.Сообщение = "Запись лога";
НовыйЛог.Записать();
КонецЦикла;
ЗафиксироватьТранзакцию();
Исключение
ОтменитьТранзакцию();
ВызватьИсключение;
КонецПопытки;
Если вы работаете на современных версиях платформы 1С, вам доступен новый продвинутый функционал записи независимых регистров сведений. В метод Записать() вместо простого флага Замещение (Истина/Ложь) был добавлен параметр РежимЗамещения (системное перечисление ReplacementMode).
Теперь разработчик может гибко управлять процессом записи:
Использование режима РежимЗамещения.Добавление делает работу с регистрами сведений еще более производительной при решении задач логирования данных.