Представим ситуацию: в один прекрасный день в вашей базе «1С:Управление торговлей» (или любой другой конфигурации) коды некоторых элементов справочников — будь то Номенклатура, Контрагенты или Организации — внезапно заменяются на нули. Например, был рабочий код 09395, а стал 00000. При этом новые элементы создаются корректно, автонумерация не нарушена, стандартное тестирование и исправление базы не дает результатов, а Журнал регистрации 1С абсолютно чист.
Такая проблема часто возникает в базах с большой историей (более 10 лет), работающих в клиент-серверном варианте под управлением MS SQL Server. В этой статье мы подробно разберем, почему происходит скрытое обнуление кодов и какими техническими средствами можно вычислить источник проблемы, даже если стандартные инструменты 1С бессильны.
Это самая распространенная программная причина, по которой «молчит» Журнал регистрации и не срабатывают ваши доработки по отслеживанию изменений. Когда сторонний код (внешняя обработка, регламентное задание, интеграция через COM-соединение или API) записывает объект с установленным свойством ОбменДанными.Загрузка = Истина, платформа 1С отключает практически все прикладные проверки.
При этом происходят следующие процессы:
ПередЗаписью и ПриЗаписи в модуле самого объекта.00000) в соответствии с заданной длиной кода.Давайте проанализируем, как может выглядеть опасный участок кода во внешней обработке:
// Пример некорректного обновления реквизитов во внешнем коде
ЭлементОбъект = СсылкаНаЭлемент.ПолучитьОбъект();
ЭлементОбъект.ОбменДанными.Загрузка = Истина;
// Если при заполнении реквизитов забыли про поле Код, либо передали пустую строку:
ЭлементОбъект.Код = "";
ЭлементОбъект.Записать(); // Запись пройдет без проверок уникальности и без логирования!
Если база работает на MS SQL Server, изменения в таблицы могут вноситься напрямую, минуя сервер приложений 1С. Это может делать интеграционное программное обеспечение (например, WMS-система, складское оборудование, сайт интернет-магазина или BI-аналитика) через ADO/ODBC-соединения или SQL-триггеры.
Поскольку запрос UPDATE выполняется непосредственно на уровне СУБД, платформа 1С физически не способна зафиксировать это событие в Журнале регистрации. Более того, при некорректном сопоставлении типов (например, если внешнее приложение передает число 0 вместо строки '00000' в поле _Code), MS SQL автоматически приведет значение к строковому типу СУБД, заполнив его нулями.
В базах со стажем более 10 лет и объемом исчисляемым десятками гигабайт могут возникать скрытые разрушения страниц данных или индексов (corruption) в MS SQL Server. Если поврежден индекс по полям _Code или _IDRRef (ссылка на элемент), СУБД при операциях обновления сопутствующих реквизитов может некорректно идентифицировать записи или записывать дефолтные значения (нули для строк фиксированной длины).
---
Давайте попробуем перехватить изменение кода средствами самой платформы. Для этого мы создадим подписку на событие, которая будет срабатывать ВСЕГДА, даже если включен режим ОбменДанными.Загрузка = Истина. Обратите внимание, что стандартные обработчики при загрузке отключаются, но мы можем написать подписку, которая умышленно игнорирует этот флаг для ведения логов.
Выполним следующие действия в конфигураторе:
_ДиагностикаСобытий) с установленными свойствами «Сервер» и «Внешнее соединение»._ДиагностикаЗаписиНоменклатуры).СправочникОбъект.Номенклатура (и аналогично для других проблемных справочников).ПриЗаписи.Проанализируем программный код, который необходимо поместить в созданный общий модуль:
Процедура _ДиагностикаЗаписиНоменклатурыПриЗаписи(Источник, Отказ) Экспорт
// Внимание: мы НЕ делаем проверку на Источник.ОбменДанными.Загрузка!
// Наша цель — поймать запись в любом режиме.
Если Источник.Код = "00000" Или СокрЛП(Источник.Код) = "" Тогда
// Формируем детальное описание события
ИнформацияОЗаписи = "--- Фиксация обнуления кода ---" + Символы.ПС;
ИнформацияОЗаписи = ИнформацияОЗаписи + "Время: " + ТекущаяДата() + Символы.ПС;
ИнформацияОЗаписи = ИнформацияОЗаписи + "Элемент: " + Источник.Ссылка.УникальныйИдентификатор() + " (" + Источник.Наименование + ")" + Символы.ПС;
ИнформацияОЗаписи = ИнформацияОЗаписи + "Режим загрузки: " + ?(Источник.ОбменДанными.Загрузка, "Да", "Нет") + Символы.ПС;
// Попытаемся получить имя пользователя и стек вызова, если это возможно
Попытка
ИнформацияОЗаписи = ИнформацияОЗаписи + "Пользователь: " + ПараметрыСеанса.ТекущийПользователь + Символы.ПС;
Исключение
ИнформацияОЗаписи = ИнформацияОЗаписи + "Пользователь: Не удалось определить сеанс" + Символы.ПС;
КонецПопытки;
// Записываем лог в текстовый файл на сервере
ПутьКЛогу = "C:\temp\1c_code_audit_log.txt";
ЗаписьТекста = Новый ЗаписьТекста(ПутьКЛогу, КодировкаТекста.UTF8, , Истина);
ЗаписьТекста.ЗаписатьСтроку(ИнформацияОЗаписи);
ЗаписьТекста.Закрыть();
КонецЕсли;
КонецПроцедуры
Очень часто причиной проблемы становится забытый код прошлых разработчиков или некорректно работающие внешние обработки. Разберем, по каким ключевым словам нужно провести глобальный поиск по конфигурации (клавиши Ctrl + Shift + F):
Выполнить( — часто используется для запуска динамического кода, хранящегося во внешних файлах или справочнике «Внешние обработки»..Загрузка = Истина — найдет все места, где записи производятся в обход стандартных проверок..Код = — проанализируйте все места ручного присвоения кодов элементам справочников. Особенно обратите внимание на конструкции, где код собирается из частей или форматируется.Если диагностика средствами 1С (Шаг 1) ничего не записала в файл, значит, изменения происходят напрямую на уровне MS SQL Server. В этом случае единственным надежным способом поймать виновника является создание триггера непосредственно в базе данных SQL.
Для начала нам нужно узнать имя таблицы справочника в СУБД. Выясним его, выполнив в 1С простой код (например, через табло или внешнюю обработку):
Сообщить(ПолучитьСтруктуруХраненияБазыДанных().Найти("Справочник.Номенклатура").ИмяТаблицыХранения);
// Например, функция вернет имя таблицы: dbo._Reference84
Теперь откроем SQL Server Management Studio (SSMS), подключимся к нашей базе данных и создадим триггер, который будет срабатывать при любом изменении поля _Code в этой таблице. Напишем и выполним следующий SQL-скрипт:
-- Создаем таблицу для логов, если ее еще нет
IF OBJECT_ID('dbo.Audit_Code_Changes', 'U') IS NULL
BEGIN
CREATE TABLE dbo.Audit_Code_Changes (
LogID INT IDENTITY(1,1) PRIMARY KEY,
ObjectID VARBINARY(16),
OldCode NVARCHAR(100),
NewCode NVARCHAR(100),
ChangeDate DATETIME DEFAULT GETDATE(),
AppName NVARCHAR(256),
HostName NVARCHAR(256),
SQLUser NVARCHAR(128)
);
END;
GO
-- Создаем сам триггер на таблицу Номенклатуры (замените _Reference84 на ваше имя таблицы)
CREATE OR ALTER TRIGGER trg_Audit_Nomenclature_Code
ON dbo._Reference84
AFTER UPDATE
AS
BEGIN
-- Проверяем, изменилось ли поле Кода (_Code)
IF UPDATE(_Code)
BEGIN
INSERT INTO dbo.Audit_Code_Changes (ObjectID, OldCode, NewCode, AppName, HostName, SQLUser)
SELECT
i._IDRRef,
d._Code,
i._Code,
APP_NAME(), -- Имя приложения (поможет понять, это сеанс 1С или внешняя утилита)
HOST_NAME(), -- Имя компьютера, с которого пришел запрос
SYSTEM_USER -- Системный пользователь SQL
FROM deleted d
JOIN inserted i ON d._IDRRef = i._IDRRef
WHERE i._Code = '00000' OR LTRIM(RTRIM(i._Code)) = ''; -- Логируем только обнуления
END
END;
GO
После создания этого триггера при первой же попытке обнулить код (изнутри 1С или снаружи) в таблице dbo.Audit_Code_Changes появится запись. По полю AppName вы мгновенно поймете природу вызова (например, там будет указан фоновый процесс rphost платформы 1С или сторонняя программа, например Excel/Python/ADO-клиент), а поле HostName укажет на конкретный рабочий компьютер в сети.
Если вы подозреваете, что причиной сбоя является повреждение индексов СУБД, обязательно запустите проверку целостности базы данных на сервере MS SQL. Для этого выполните следующую команду в SSMS:
-- Запуск полной проверки физической целостности базы данных
DBCC CHECKDB ('Имя_Вашей_Базы_Данных') WITH NO_INFOMSGS, ALL_ERRORMSGS;
Если утилита сообщит об ошибках распределения (allocation errors) или согласованности (consistency errors) в таблицах ваших справочников, необходимо незамедлительно провести процедуру исправления базы (в режиме монопольного доступа) или сделать выгрузку/загрузку файла базы данных (.dt) для полного перестроения таблиц и индексов средствами платформы 1С.