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

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

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

Проанализируем природу этого сбоя, выясним причину расхождения данных и подробно разберем по шагам надежные варианты восстановления целостности информационной базы.

Почему возникают дубли в непериодическом регистре сведений

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

Однако при сбоях дисковой подсистемы, аварийных перезагрузках сервера без защиты кэша записи (особенно на потребительских SSD-накопителях без встроенных конденсаторов PLP) или некорректных настройках синхронизации (параметры fsync и synchronous_commit) может произойти повреждение дерева B-Tree уникального индекса в PostgreSQL.

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

Разберем, почему форма регистра и запрос ведут себя по-разному:

  1. Динамический список формы: использует постраничную выборку порциями и внутренний кэш строк, ориентируясь на ключевые поля. Обнаружив строки с одинаковыми идентификаторами, механизм позиционирования платформы сбивается, что приводит к размножению одной и той же записи на экране.
  2. Запрос к регистру: выполняет сканирование таблицы и добросовестно возвращает все существующие физические строки, включая дубли.
  3. Команда «Вывести список»: может выполнять последовательное чтение или использовать агрегацию, отсекая видимые дубликаты на клиенте.

Способ 1. Пакетная перезапись элементов-владельцев средствами 1С

Рассмотрим самый безопасный метод, который не требует остановки сервера, прямого вмешательства в структуру СУБД и гарантирует корректную работу всей прикладной бизнес-логики конфигурации.

В большинстве типовых решений контактная информация и аналогичные регистры подчинены ведущим объектам (например, элементам справочника Контрагенты). При штатной записи элемента справочника платформа формирует и записывает соответствующий НаборЗаписей регистра с установленным свойством Замещение = Истина. В момент записи СУБД удаляет из физической таблицы все имеющиеся строки по отбору (устраняя тем самым и оригинал, и поврежденный дубль), после чего заново вставляет одну валидную строку.

Разберем по шагам создание внешней обработки для пакетной перезаписи всех объектов:

  1. Откроем конфигуратор или создадим внешнюю обработку.
  2. Напишем код последовательной выборки и перезаписи элементов справочника-владельца.
  3. Выполним обработку в пользовательском режиме.

Посмотрим на пример программного кода для пакетной обработки справочника Контрагенты:


Выборка = Справочники.Контрагенты.Выбрать();
ВсегоОбработано = 0;

Пока Выборка.Следующий() Цикл
    ОбъектКонтрагент = Выборка.ПолучитьОбъект();
    Если ОбъектКонтрагент <> Неопределено Тогда
        Попытка
            // Запись объекта инициирует перезапись наборов контактной информации с замещением
            ОбъектКонтрагент.Записать();
            ВсегоОбработано = ВсегоОбработано + 1;
        Исключение
            Сообщить("Ошибка при записи контрагента: " + ОбъектКонтрагент.Наименование + " | " + ОписаниеОшибки());
        КонецПопытки;
    КонецЕсли;
КонецЦикла;

Сообщить("Обработка завершена. Всего перезаписано объектов: " + ВсегоОбработано);

Способ 2. Точечное удаление дублей через набор записей регистра

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

Разберем алгоритм решения:

  1. С помощью языка запросов 1С выявим только те комбинации измерений, количество которых в таблице строго больше единицы (предложение ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1).
  2. Для каждого проблемного набора считаем данные в НаборЗаписей, отфильтруем дубли и запишем очищенный набор с флагом замещения.

Посмотрим на пример реализации данного алгоритма:


Запрос = Новый Запрос;
Запрос.Текст = 
    "ВЫБРАТЬ
    |	КИ.Объект КАК Объект,
    |	КИ.Тип КАК Тип,
    |	КИ.Вид КАК Вид,
    |	КОЛИЧЕСТВО(*) КАК КоличествоСтрок
    |ИЗ
    |	РегистрСведений.КонтактнаяИнформация КАК КИ
    |СГРУППИРОВАТЬ ПО
    |	КИ.Объект,
    |	КИ.Тип,
    |	КИ.Вид
    |ИМЕЮЩИЕ
    |	КОЛИЧЕСТВО(*) > 1";

Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();

Пока Выборка.Следующий() Цикл
    Набор = РегистрыСведений.КонтактнаяИнформация.СоздатьНаборЗаписей();
    Набор.Отбор.Объект.Установить(Выборка.Объект);
    Набор.Отбор.Тип.Установить(Выборка.Тип);
    Набор.Отбор.Вид.Установить(Выборка.Вид);
    
    // Считываем записи регистра по отбору
    Набор.Прочитать();
    
    // Если записей больше одной, оставляем только первую корректную строку
    Пока Набор.Количество() > 1 Цикл
        Набор.Удалить(Набор.Количество() - 1);
    КонецЦикла;
    
    // Записываем набор с замещением, удаляя битые дубли из таблицы
    Набор.Записать(Истина);
КонецЦикла;

Способ 3. Очистка дубликатов на уровне СУБД PostgreSQL

В критических ситуациях, когда операции 1С завершаются системными авариями или база данных не дает выполнить чтение из-за поврежденных страниц, прибегнем к исправлению на уровне SQL. Важно: перед выполнением любых прямых манипуляций с базой необходимо создать резервную копию.

Разберем по шагам действия администратора СУБД:

  1. Выясним физическое имя таблицы регистра и полей измерений. В конфигураторе 1С выполним команду ПолучитьСтруктуруХраненияБазыДанных(). Таблица регистра сведений получит имя вида _inforgXXXX.
  2. Подключимся к базе данных через утилиту командной строки psql или графический клиент.
  3. Удалим физические дубликаты строк, оставив только одну запись для каждой комбинации измерений. Для этого используем системный идентификатор физического расположения строки ctid.

Выполним запрос удаления дублей в PostgreSQL:


DELETE FROM _inforgXXXX
WHERE ctid NOT IN (
    SELECT min(ctid)
    FROM _inforgXXXX
    GROUP BY _fldYYYY_type, _fldYYYY_rtref, _fldYYYY_rrref, _fldZZZZ
);

После удаления дубликатов обязательно перестроим поврежденные индексы таблицы, выполнив команду:


REINDEX TABLE _inforgXXXX;
VACUUM FULL ANALYZE _inforgXXXX;

Обязательные регламентные процедуры после исправления

После того как дубликаты физически удалены, необходимо восстановить логическую и физическую целостность базы данных средствами самой платформы. Если пропустить этот этап, попытка сделать выгрузку информационной базы в файл .dt или выполнить реструктуризацию может аварийно завершиться ошибкой нарушения уникальности ключа.

Выполним следующие операции:

  1. Запустим базу данных в режиме «Конфигуратор».
  2. Откроем меню Администрирование > Тестирование и исправление...
  3. Установим флажки напротив пунктов:
    1. Реиндексация таблиц информационной базы;
    2. Проверка логической целостности информационной базы;
    3. Проверка физической целостности таблиц.
  4. Нажмем кнопку Выполнить и дождемся завершения процедуры.
  5. Остановим службу сервера 1С:Предприятия и очистим каталоги сеансового кэша на сервере (каталоги с маской snacc*), чтобы исключить показ устаревших представлений динамическими списками.

Рекомендации по настройке postgresql.conf

Проанализируем параметры конфигурационного файла СУБД, чтобы исключить повреждение индексов в будущем:

  1. Параметр fsync = on должен быть строго включен. Отключение сброса кэша на диск практически гарантирует повреждение структур данных при любом внезапном сбое питания.
  2. Параметр synchronous_commit: хотя значение off существенно ускоряет групповые транзакции в 1С, на ненадежных дисковых контроллерах без батарейного питания (BBU) оно увеличивает риск рассогласования страниц. При наличии частых сбоев рекомендуется вернуть значение on.
  3. Параметр full_page_writes = on: обязательно должен быть включен для надежного восстановления страниц после аварийных сбоев ОС.
  4. Если база размещена на дисках SSD, убедимся, что на сервере используется отказоустойчивое оборудование серверного класса с защитой от внезапного обесточивания.
← На главную