Почему обнуляются коды справочников в 1С и как найти скрытого виновника?

Программист 1С v8.3 (Обычные формы) 1С:Управление торговлей Управленческий учет Торговля и дистрибуция
← На главную

Представим ситуацию: в один прекрасный день в вашей базе «1С:Управление торговлей» (или любой другой конфигурации) коды некоторых элементов справочников — будь то Номенклатура, Контрагенты или Организации — внезапно заменяются на нули. Например, был рабочий код 09395, а стал 00000. При этом новые элементы создаются корректно, автонумерация не нарушена, стандартное тестирование и исправление базы не дает результатов, а Журнал регистрации 1С абсолютно чист.

Такая проблема часто возникает в базах с большой историей (более 10 лет), работающих в клиент-серверном варианте под управлением MS SQL Server. В этой статье мы подробно разберем, почему происходит скрытое обнуление кодов и какими техническими средствами можно вычислить источник проблемы, даже если стандартные инструменты 1С бессильны.

Причина 1: Использование механизма ОбменДанными.Загрузка = Истина

Это самая распространенная программная причина, по которой «молчит» Журнал регистрации и не срабатывают ваши доработки по отслеживанию изменений. Когда сторонний код (внешняя обработка, регламентное задание, интеграция через COM-соединение или API) записывает объект с установленным свойством ОбменДанными.Загрузка = Истина, платформа 1С отключает практически все прикладные проверки.

При этом происходят следующие процессы:

  1. Блокируется выполнение обработчиков событий ПередЗаписью и ПриЗаписи в модуле самого объекта.
  2. Игнорируется большинство подписок на события, если в них явно не прописан обход этого режима.
  3. Данные записываются напрямую в базу, и если в коде внешней обработки была логическая ошибка (например, при обновлении реквизитов код элемента передавался пустым), платформа преобразует пустое строковое поле в строку из нулей (например, 00000) в соответствии с заданной длиной кода.

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


// Пример некорректного обновления реквизитов во внешнем коде
ЭлементОбъект = СсылкаНаЭлемент.ПолучитьОбъект();
ЭлементОбъект.ОбменДанными.Загрузка = Истина;
// Если при заполнении реквизитов забыли про поле Код, либо передали пустую строку:
ЭлементОбъект.Код = ""; 
ЭлементОбъект.Записать(); // Запись пройдет без проверок уникальности и без логирования!

Причина 2: Прямые манипуляции на уровне СУБД (MS SQL Server)

Если база работает на MS SQL Server, изменения в таблицы могут вноситься напрямую, минуя сервер приложений 1С. Это может делать интеграционное программное обеспечение (например, WMS-система, складское оборудование, сайт интернет-магазина или BI-аналитика) через ADO/ODBC-соединения или SQL-триггеры.

Поскольку запрос UPDATE выполняется непосредственно на уровне СУБД, платформа 1С физически не способна зафиксировать это событие в Журнале регистрации. Более того, при некорректном сопоставлении типов (например, если внешнее приложение передает число 0 вместо строки '00000' в поле _Code), MS SQL автоматически приведет значение к строковому типу СУБД, заполнив его нулями.

Причина 3: Повреждение индексов таблиц на физическом уровне

В базах со стажем более 10 лет и объемом исчисляемым десятками гигабайт могут возникать скрытые разрушения страниц данных или индексов (corruption) в MS SQL Server. Если поврежден индекс по полям _Code или _IDRRef (ссылка на элемент), СУБД при операциях обновления сопутствующих реквизитов может некорректно идентифицировать записи или записывать дефолтные значения (нули для строк фиксированной длины).

---

Шаг 1: Настройка диагностической подписки на события в 1С

Давайте попробуем перехватить изменение кода средствами самой платформы. Для этого мы создадим подписку на событие, которая будет срабатывать ВСЕГДА, даже если включен режим ОбменДанными.Загрузка = Истина. Обратите внимание, что стандартные обработчики при загрузке отключаются, но мы можем написать подписку, которая умышленно игнорирует этот флаг для ведения логов.

Выполним следующие действия в конфигураторе:

  1. Создадим новый общий модуль (например, _ДиагностикаСобытий) с установленными свойствами «Сервер» и «Внешнее соединение».
  2. Создадим новую Подписку на событие (например, _ДиагностикаЗаписиНоменклатуры).
  3. В качестве источника выберем СправочникОбъект.Номенклатура (и аналогично для других проблемных справочников).
  4. Событие укажем как ПриЗаписи.

Проанализируем программный код, который необходимо поместить в созданный общий модуль:


Процедура _ДиагностикаЗаписиНоменклатурыПриЗаписи(Источник, Отказ) Экспорт
    
    // Внимание: мы НЕ делаем проверку на Источник.ОбменДанными.Загрузка!
    // Наша цель — поймать запись в любом режиме.
    
    Если Источник.Код = "00000" Или СокрЛП(Источник.Код) = "" Тогда
        
        // Формируем детальное описание события
        ИнформацияОЗаписи = "--- Фиксация обнуления кода ---" + Символы.ПС;
        ИнформацияОЗаписи = ИнформацияОЗаписи + "Время: " + ТекущаяДата() + Символы.ПС;
        ИнформацияОЗаписи = ИнформацияОЗаписи + "Элемент: " + Источник.Ссылка.УникальныйИдентификатор() + " (" + Источник.Наименование + ")" + Символы.ПС;
        ИнформацияОЗаписи = ИнформацияОЗаписи + "Режим загрузки: " + ?(Источник.ОбменДанными.Загрузка, "Да", "Нет") + Символы.ПС;
        
        // Попытаемся получить имя пользователя и стек вызова, если это возможно
        Попытка
            ИнформацияОЗаписи = ИнформацияОЗаписи + "Пользователь: " + ПараметрыСеанса.ТекущийПользователь + Символы.ПС;
        Исключение
            ИнформацияОЗаписи = ИнформацияОЗаписи + "Пользователь: Не удалось определить сеанс" + Символы.ПС;
        КонецПопытки;
        
        // Записываем лог в текстовый файл на сервере
        ПутьКЛогу = "C:\temp\1c_code_audit_log.txt";
        ЗаписьТекста = Новый ЗаписьТекста(ПутьКЛогу, КодировкаТекста.UTF8, , Истина);
        ЗаписьТекста.ЗаписатьСтроку(ИнформацияОЗаписи);
        ЗаписьТекста.Закрыть();
        
    КонецЕсли;
    
КонецПроцедуры

Шаг 2: Поиск «зловредного» кода через глобальный поиск

Очень часто причиной проблемы становится забытый код прошлых разработчиков или некорректно работающие внешние обработки. Разберем, по каким ключевым словам нужно провести глобальный поиск по конфигурации (клавиши Ctrl + Shift + F):

Шаг 3: Диагностика на уровне СУБД с помощью SQL-триггера

Если диагностика средствами 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 укажет на конкретный рабочий компьютер в сети.

Шаг 4: Проверка физической целостности базы данных

Если вы подозреваете, что причиной сбоя является повреждение индексов СУБД, обязательно запустите проверку целостности базы данных на сервере MS SQL. Для этого выполните следующую команду в SSMS:


-- Запуск полной проверки физической целостности базы данных
DBCC CHECKDB ('Имя_Вашей_Базы_Данных') WITH NO_INFOMSGS, ALL_ERRORMSGS;

Если утилита сообщит об ошибках распределения (allocation errors) или согласованности (consistency errors) в таблицах ваших справочников, необходимо незамедлительно провести процедуру исправления базы (в режиме монопольного доступа) или сделать выгрузку/загрузку файла базы данных (.dt) для полного перестроения таблиц и индексов средствами платформы 1С.

← На главную