Каждый администратор баз данных и разработчик 1С рано или поздно сталкивается с ситуацией, когда стабильно работающая система после выполнения регламентных процедур (например, свертки базы данных или проведения Тестирования и Исправления) начинает давать серьезные сбои. Одним из наиболее неприятных и критических сбоев является ошибка СУБД: «Попытка вставки неуникального значения в уникальный индекс» (в терминологии MS SQL Server это звучит как Cannot insert duplicate key row in object... with unique index...).
Эта ошибка полностью блокирует проведение документов, нарушает бизнес-процессы и вызывает панику у пользователей. В рамках данной статьи мы подробно разберем, почему возникает эта проблема, как провести ее глубокую диагностику на уровне СУБД и какими методами можно гарантированно ее устранить.
Для того чтобы эффективно бороться с проблемой, нам необходимо понять физику процесса. Платформа 1С:Предприятие хранит данные в таблицах реляционных СУБД (MS SQL Server, PostgreSQL, IBM DB2 или Oracle). Для обеспечения быстрого поиска данных и поддержания логической целостности платформа создает для таблиц индексы. Некоторые из этих индексов являются уникальными (Unique Indexes).
Уникальный индекс гарантирует, что в таблице не может существовать двух строк с абсолютно одинаковым набором значений в ключевых полях. Например, в регистрах накопления и бухгалтерии уникальность записи в таблице движений обычно определяется комбинацией полей: Период + Регистратор + НомерСтроки (и, в некоторых случаях, дополнительных разделителей).
После свертки базы данных и последующего Тестирования и Исправления (ТиИ) данная ошибка чаще всего возникает по трем основным причинам:
УстановитьСсылкуНового(). Если логика переноса нарушена, новому объекту может быть присвоена ссылка, которая уже физически существует в базе данных или связана с другими записями в таблицах движений.<Объект не найден>). При запуске ТиИ с включенной опцией очистки битых ссылок платформа пытается привести эти поля к значению NULL или к пустому значению неопределенного типа. В результате СУБД видит строки, у которых ключевые поля стали одинаковыми (например, несколько строк с пустыми субконто или пустыми регистраторами), и при попытке записать их в уникальный индекс выдает ошибку.Давайте подробно рассмотрим методы решения этой проблемы, начиная с простых административных шагов в пользовательском режиме и заканчивая низкоуровневой работой непосредственно в консоли СУБД.
Если ошибка уникального индекса локализована в таблицах итогов регистров накопления или бухгалтерии (это можно понять, если проведение документов спотыкается на расчете итогов текущего месяца), мы можем применить встроенные механизмы платформы.
Рассмотрим по шагам данный алгоритм работы:
Хозрасчетный или проблемный Регистр накопления)._AccTtl или _AccumRegTotals) и удалит их индексы.Если база данных имеет внушительный объем и выгрузка в файловый вариант (формат .dt) невозможна из-за ошибок или ограничений памяти, нам придется работать напрямую с СУБД (MS SQL Server или PostgreSQL).
Проанализируем ситуацию: для начала нам нужно точно локализовать имя таблицы и имя индекса, на которых происходит сбой. Полный текст ошибки содержит технические имена СУБД, например, таблица _AccRg14320 и уникальный индекс _AccRg14320_ByPeriod_TRNNG.
Для определения того, какому объекту метаданных 1С соответствует данная таблица в SQL, выполним следующий код в консоли отладки 1С:
СтруктураБазы = ПолучитьСтруктуруХраненияБазыДанных();
Для Каждого СтрокаТаблицы Из СтруктураБазы Цикл
Если СтрокаТаблицы.ИмяТаблицыХранения = "_AccRg14320" Тогда
Сообщить("Проблемная таблица принадлежит объекту: " + СтрокаТаблицы.ИмяИменитогоОбъектаМетаданных);
КонецЕсли;
КонецЦикла;
После того как объект метаданных идентифицирован, нам необходимо найти дублирующиеся записи в SQL-таблице, которые нарушают требования уникального индекса. Перейдем в среду СУБД (например, SQL Server Management Studio) и выполним запрос к проблемной таблице движений или итогов. Мы сгруппируем записи по ключевым полям индекса и выведем те группы, количество записей в которых превышает единицу.
Пример SQL-запроса для поиска дублей в регистре сведений или регистре движений:
SELECT COUNT(*), _Period, _RecorderTRef, _RecorderRRef, _LineNo
FROM _AccRg14320
GROUP BY _Period, _RecorderTRef, _RecorderRRef, _LineNo
HAVING COUNT(*) > 1;
Разберем по шагам, что делать, если запрос вернул строки с дублями:
Если проблема вызвана некорректно созданными ссылками на этапе программного переноса данных, нам необходимо предотвратить появление дубликатов на уровне встроенного языка 1С. Рассмотрим пример правильного назначения ссылки новому документу, чтобы избежать конфликтов уникальности индексов в СУБД.
Посмотрим на пример кода, гарантирующего уникальность ссылки нового документа при программном создании:
НовыйДокумент = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();
// Генерируем новый уникальный идентификатор (GUID)
НовыйИдентификатор = Новый УникальныйИдентификатор();
НоваяСсылка = Документы.ПоступлениеТоваровУслуг.ПолучитьСсылку(НовыйИдентификатор);
// Проверяем, нет ли уже в базе данных объекта с такой ссылкой
Если Документы.ПоступлениеТоваровУслуг.НайтиПоСсылка(НоваяСсылка) = Документы.ПоступлениеТоваровУслуг.ПустаяСсылка() Тогда
НовыйДокумент.УстановитьСсылкуНового(НоваяСсылка);
НовыйДокумент.Записать(РежимЗаписиДокумента.Запись);
Иначе
ВызватьИсключение "Сгенерированная ссылка уже существует в базе данных! Операция прервана.";
КонецЕсли;
Также проанализируем ситуацию, когда в регистре бухгалтерии возникают битые ссылки на субконто со значением NULL в СУБД. Нам необходимо принудительно очищать или приводить некорректные типы значений в обработчике записи регистра. Рассмотрим пример исправления субконто в модуле набора записей регистра бухгалтерии перед его физической записью в базу данных:
Процедура ПередЗаписью(Отказ, Замещение)
Если ОбменДанными.Загрузка Тогда
// Обязательно проверяем корректность субконто при переносе остатков
Для Каждого Запись Из ЭтотОбъект Цикл
Для НомерСубконто = 1 По 3 Цикл
КлючСубконто = "Субконто" + НомерСубконто;
Если Запись[КлючСубконто] = Неопределено Или Запись[КлючСубконто] = NULL Тогда
// Приводим некорректное значение к пустой ссылке определенного типа
// или очищаем его в соответствии с настройками счета учета
СчетУчета = Запись.Счет;
Если СчетУчета.ВидыСубконто.Количество() >= НомерСубконто Тогда
ТипСубконто = СчетУчета.ВидыСубконто[НомерСубконто - 1].ВидСубконто.ТипЗначения;
Запись[КлючСубконто] = ТипСубконто.ПривестиЗначение(Неопределено);
КонецЕсли;
КонецЕсли;
КонецЦикла;
КонецЦикла;
КонецЕсли;
КонецПроцедуры
Использование метода ПривестиЗначение() позволяет гарантировать, что в физическую таблицу СУБД запишется корректная типизированная пустая ссылка (например, пустая ссылка справочника «Контрагенты»), а не значение неопределенного типа или системный NULL, который при группировке записей СУБД вызовет ошибку уникального индекса и прервет транзакцию проведения документов.
Для предотвращения подобных сбоев при проведении крупных технических работ по свертке баз данных придерживайтесь следующих правил:
.bak) перед запуском процедур свертки или Тестирования и Исправления..dt, который чувствителен к лимитам памяти и может искажать структуру индексов при чтении.