В процессе эксплуатации клиент-серверной базы 1С:Предприятия под управлением СУБД PostgreSQL администраторы и разработчики могут столкнуться с аномальным поведением регистров сведений. Проявляется это следующим образом: при открытии списка регистра в режиме «1С:Предприятие» строки визуально задваиваются, утраиваются либо одна и та же строка циклически заполняет весь экран. В то же время простой запрос к таблице регистра возвращает в два раза больше строк, чем содержится реальных объектов в системе, хотя штатная команда «Вывести список» может показывать корректное значение.
Проанализируем природу этого сбоя, выясним причину расхождения данных и подробно разберем по шагам надежные варианты восстановления целостности информационной базы.
В платформе 1С:Предприятие уникальность записей регистра сведений контролируется на уровне СУБД с помощью уникального первичного индекса, построенного по ключевым полям — измерениям (и периоду, если регистр периодический). В штатной ситуации СУБД физически не позволяет вставить две строки с одинаковым набором измерений, выбрасывая исключение нарушения уникальности.
Однако при сбоях дисковой подсистемы, аварийных перезагрузках сервера без защиты кэша записи (особенно на потребительских SSD-накопителях без встроенных конденсаторов PLP) или некорректных настройках синхронизации (параметры fsync и synchronous_commit) может произойти повреждение дерева B-Tree уникального индекса в PostgreSQL.
При повреждении индекса СУБД выполняет проверку существования ключа по искаженному дереву, не находит уже существующую запись и разрешает вставку дублирующей строки в физическую страницу таблицы. В итоге таблица базы данных начинает содержать две и более строки с абсолютно одинаковым ключом.
Разберем, почему форма регистра и запрос ведут себя по-разному:
Рассмотрим самый безопасный метод, который не требует остановки сервера, прямого вмешательства в структуру СУБД и гарантирует корректную работу всей прикладной бизнес-логики конфигурации.
В большинстве типовых решений контактная информация и аналогичные регистры подчинены ведущим объектам (например, элементам справочника Контрагенты). При штатной записи элемента справочника платформа формирует и записывает соответствующий НаборЗаписей регистра с установленным свойством Замещение = Истина. В момент записи СУБД удаляет из физической таблицы все имеющиеся строки по отбору (устраняя тем самым и оригинал, и поврежденный дубль), после чего заново вставляет одну валидную строку.
Разберем по шагам создание внешней обработки для пакетной перезаписи всех объектов:
Посмотрим на пример программного кода для пакетной обработки справочника Контрагенты:
Выборка = Справочники.Контрагенты.Выбрать();
ВсегоОбработано = 0;
Пока Выборка.Следующий() Цикл
ОбъектКонтрагент = Выборка.ПолучитьОбъект();
Если ОбъектКонтрагент <> Неопределено Тогда
Попытка
// Запись объекта инициирует перезапись наборов контактной информации с замещением
ОбъектКонтрагент.Записать();
ВсегоОбработано = ВсегоОбработано + 1;
Исключение
Сообщить("Ошибка при записи контрагента: " + ОбъектКонтрагент.Наименование + " | " + ОписаниеОшибки());
КонецПопытки;
КонецЕсли;
КонецЦикла;
Сообщить("Обработка завершена. Всего перезаписано объектов: " + ВсегоОбработано);
Если регистр сведений не привязан жестко к перезаписи объекта или объем базы слишком велик для полной перезаписи справочников, выполним точечную очистку проблемных записей с помощью запроса.
Разберем алгоритм решения:
ИМЕЮЩИЕ КОЛИЧЕСТВО(*) > 1).НаборЗаписей, отфильтруем дубли и запишем очищенный набор с флагом замещения.Посмотрим на пример реализации данного алгоритма:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| КИ.Объект КАК Объект,
| КИ.Тип КАК Тип,
| КИ.Вид КАК Вид,
| КОЛИЧЕСТВО(*) КАК КоличествоСтрок
|ИЗ
| РегистрСведений.КонтактнаяИнформация КАК КИ
|СГРУППИРОВАТЬ ПО
| КИ.Объект,
| КИ.Тип,
| КИ.Вид
|ИМЕЮЩИЕ
| КОЛИЧЕСТВО(*) > 1";
Результат = Запрос.Выполнить();
Выборка = Результат.Выбрать();
Пока Выборка.Следующий() Цикл
Набор = РегистрыСведений.КонтактнаяИнформация.СоздатьНаборЗаписей();
Набор.Отбор.Объект.Установить(Выборка.Объект);
Набор.Отбор.Тип.Установить(Выборка.Тип);
Набор.Отбор.Вид.Установить(Выборка.Вид);
// Считываем записи регистра по отбору
Набор.Прочитать();
// Если записей больше одной, оставляем только первую корректную строку
Пока Набор.Количество() > 1 Цикл
Набор.Удалить(Набор.Количество() - 1);
КонецЦикла;
// Записываем набор с замещением, удаляя битые дубли из таблицы
Набор.Записать(Истина);
КонецЦикла;
В критических ситуациях, когда операции 1С завершаются системными авариями или база данных не дает выполнить чтение из-за поврежденных страниц, прибегнем к исправлению на уровне SQL. Важно: перед выполнением любых прямых манипуляций с базой необходимо создать резервную копию.
Разберем по шагам действия администратора СУБД:
ПолучитьСтруктуруХраненияБазыДанных(). Таблица регистра сведений получит имя вида _inforgXXXX.psql или графический клиент.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 или выполнить реструктуризацию может аварийно завершиться ошибкой нарушения уникальности ключа.
Выполним следующие операции:
snacc*), чтобы исключить показ устаревших представлений динамическими списками.Проанализируем параметры конфигурационного файла СУБД, чтобы исключить повреждение индексов в будущем:
fsync = on должен быть строго включен. Отключение сброса кэша на диск практически гарантирует повреждение структур данных при любом внезапном сбое питания.synchronous_commit: хотя значение off существенно ускоряет групповые транзакции в 1С, на ненадежных дисковых контроллерах без батарейного питания (BBU) оно увеличивает риск рассогласования страниц. При наличии частых сбоев рекомендуется вернуть значение on.full_page_writes = on: обязательно должен быть включен для надежного восстановления страниц после аварийных сбоев ОС.