В крупных информационных системах на базе 1С 7.7 часто возникает задача отслеживания изменений: кто, когда и что именно поменял в объекте (справочнике или документе). Ситуация осложняется, когда база работает на MS SQL Server, объем событий исчисляется миллионами, а требования к быстродействию системы крайне высоки. В версии 7.7 отсутствуют штатные механизмы вроде "Регистров сведений" или встроенного версионирования, поэтому разработчикам приходится выбирать между несколькими архитектурными решениями. Проанализируем наиболее эффективные подходы.
Это самый простой в реализации метод, который остается в рамках штатных метаданных 1С. Мы создаем справочник (например, ИсторияСостояний), где каждый элемент — это запись об изменении. Рассмотрим ключевые моменты настройки такого справочника:
ДатаСобытия и Объект. Это позволит SQL-серверу быстро фильтровать записи при построении отчетов.ГГГГММДД ЧЧ:ММ:СС.Разберем пример кода в модуле формы или объекта при записи:
Процедура ПриЗаписи()
СпрИстория = СоздатьОбъект("Справочник.ИсторияСостояний");
СпрИстория.Новый();
СпрИстория.Объект = ТекущийОбъект;
СпрИстория.ДатаСобытия = РабочаяДата();
СпрИстория.ВремяСобытия = ТекущееВремя();
СпрИстория.Автор = ИмяПользователя();
СпрИстория.Состояние = СостояниеОбъекта;
СпрИстория.Записать();
КонецПроцедуры
Если предполагается хранение миллионов записей за несколько лет, стандартные справочники могут замедлить реструктуризацию базы. Проанализируем более продвинутый вариант — использование внешних таблиц, о которых 1С "не знает" на уровне конфигуратора.
Для этого используется внешняя компонента 1C++. Мы создаем таблицу непосредственно в SQL-базе. Это позволяет избежать блокировок системных таблиц 1С (таких как _1SJOURN) при интенсивной записи логов. Рассмотрим, как может выглядеть запись в такую таблицу:
// Пример записи через 1C++ и прямой SQL
ТекстЗапроса = "
|INSERT INTO Custom_ObjectHistory (ObjID, UserID, DateEvent, NewState)
|VALUES (:ID, :User, :Date, :State)";
db = СоздатьОбъект("ODBCDatabase");
Запрос = СоздатьОбъект("ODBCRecordset");
Запрос.УстБазу(db);
Запрос.УстановитьТекстовыйПараметр("ID", ТекущийЭлемент());
Запрос.УстановитьТекстовыйПараметр("User", ИмяПользователя());
Запрос.УстановитьТекстовыйПараметр("Date", ТекущаяДата());
Запрос.УстановитьТекстовыйПараметр("State", НужноеСостояние);
Запрос.ВыполнитьИнструкцию(ТекстЗапроса);
Важное преимущество: Записи в такую таблицу можно делать асинхронно или через триггеры SQL, что практически не влияет на скорость работы пользователя в интерфейсе.
Выясним причину, по которой запись напрямую в базу в момент транзакции может быть нежелательна: если запись объекта "откатится" из-за ошибки, запись в справочнике истории (если она внутри транзакции) тоже исчезнет. Если же нам нужен "черный ящик", который фиксирует все попытки, или если мы хотим максимально разгрузить SQL в пиковые часы, используем текстовые файлы.
Рассмотрим алгоритм этого метода:
CSV или JSON-подобный формат). Операция записи в файл происходит мгновенно и не блокирует таблицы БД.Чтобы база не раздувалась, не стоит записывать все состояние объекта целиком. Рациональнее хранить только те реквизиты, которые реально изменились. Для этого перед записью необходимо сравнить текущие значения с теми, что уже лежат в базе.
Рассмотрим пример функции сравнения:
Функция ПолучитьИзменения()
ТекстИзменений = "";
СтарыйОбъект = СоздатьОбъект("Справочник." + Вид());
Если СтарыйОбъект.НайтиЭлемент(ТекущийЭлемент()) = 1 Тогда
Если СтарыйОбъект.Наименование <> Наименование Тогда
ТекстИзменений = ТекстИзменений + "Наим: " + СтарыйОбъект.Наименование + " -> " + Наименование + "; ";
КонецЕсли;
// Проверка остальных реквизитов...
КонецЕсли;
Возврат ТекстИзменений;
КонецФункции
Для обеспечения стабильной работы системы в течение 3–5 лет и более, проанализируем настройки на стороне сервера баз данных:
Секционирование таблиц (Partitioning): Если вы используете внешнюю таблицу для истории, настройте её секционирование по годам или месяцам. Это позволит хранить старые данные на медленных дешевых дисках, а актуальные — на SSD. При этом для 1С таблица останется единым объектом.
Индексы покрывающего типа: При создании индексов на таблицу истории в SQL Server, включайте в индекс часто запрашиваемые поля (например, NewValue). Это позволит SQL-серверу формировать отчеты по истории, не обращаясь к основной таблице (Index Only Scan), что ускоряет построение отчетов в десятки раз.
Проблема удаления объектов: Помните, что в 1С 7.7 при удалении элемента справочника его история может стать "бесполезной", так как ссылка превратится в "Объект не найден". Чтобы избежать этого, при записи события в историю всегда сохраняйте текстовое представление объекта (его код или наименование) в отдельном поле. Таким образом, даже через 5 лет вы сможете понять, к какому объекту относилась эта запись, даже если сам объект был удален.
Подводя итог, для реализации истории в 1С 7.7 лучшим балансом между сложностью и эффективностью является использование индексированного справочника. Однако для систем с экстремальной нагрузкой следует смотреть в сторону прямых запросов через 1C++ к отдельным SQL-таблицам вне метаданных.