Как исправить ошибку «Попытка вставки неуникального значения в уникальный индекс» в 1С?

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

Каждый администратор баз данных и разработчик 1С рано или поздно сталкивается с ситуацией, когда стабильно работающая система после выполнения регламентных процедур (например, свертки базы данных или проведения Тестирования и Исправления) начинает давать серьезные сбои. Одним из наиболее неприятных и критических сбоев является ошибка СУБД: «Попытка вставки неуникального значения в уникальный индекс» (в терминологии MS SQL Server это звучит как Cannot insert duplicate key row in object... with unique index...).

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

Выясняем причину: почему физически нарушается уникальность индексов?

Для того чтобы эффективно бороться с проблемой, нам необходимо понять физику процесса. Платформа 1С:Предприятие хранит данные в таблицах реляционных СУБД (MS SQL Server, PostgreSQL, IBM DB2 или Oracle). Для обеспечения быстрого поиска данных и поддержания логической целостности платформа создает для таблиц индексы. Некоторые из этих индексов являются уникальными (Unique Indexes).

Уникальный индекс гарантирует, что в таблице не может существовать двух строк с абсолютно одинаковым набором значений в ключевых полях. Например, в регистрах накопления и бухгалтерии уникальность записи в таблице движений обычно определяется комбинацией полей: Период + Регистратор + НомерСтроки (и, в некоторых случаях, дополнительных разделителей).

После свертки базы данных и последующего Тестирования и Исправления (ТиИ) данная ошибка чаще всего возникает по трем основным причинам:

  1. Некорректная генерация новых ссылок (GUID) при переносе остатков. При программном создании документов ввода остатков разработчики часто используют метод УстановитьСсылкуНового(). Если логика переноса нарушена, новому объекту может быть присвоена ссылка, которая уже физически существует в базе данных или связана с другими записями в таблицах движений.
  2. Появление значений NULL или пустых типов в измерениях регистров. Во время свертки документы прошлых периодов удаляются, а движения по регистрам могут остаться «осиротевшими» (со ссылкой на несуществующий регистратор <Объект не найден>). При запуске ТиИ с включенной опцией очистки битых ссылок платформа пытается привести эти поля к значению NULL или к пустому значению неопределенного типа. В результате СУБД видит строки, у которых ключевые поля стали одинаковыми (например, несколько строк с пустыми субконто или пустыми регистраторами), и при попытке записать их в уникальный индекс выдает ошибку.
  3. Рассогласование таблиц итогов регистра и таблиц самих движений. В 1С регистры состоят из физической таблицы движений (где хранятся детальные записи) и таблиц итогов (где хранятся агрегированные данные по месяцам). При проведении документа платформа пытается обновить таблицы итогов. Если в таблице движений содержатся некорректные записи, расчет итогов приводит к попытке записать дублирующийся ключ в индекс таблицы итогов.

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

Метод 1. Временное отключение разделения итогов и пересчет (самый простой путь)

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

Рассмотрим по шагам данный алгоритм работы:

  1. Запустите базу данных в режиме «1С:Предприятие» под пользователем с полными правами.
  2. Перейдите в меню Функции для технического специалиста (или через Администрирование — Обслуживание — Регламентные операции — Управление итогами и агрегатами).
  3. Найдите в списке проблемный регистр (например, Регистр бухгалтерии Хозрасчетный или проблемный Регистр накопления).
  4. Выполните действие Отключить разделение итогов для этого регистра. Механизм разделения итогов создает дополнительные субтаблицы для исключения блокировок, и при сбоях свертки именно там могут возникать дубли.
  5. Полностью отключите использование итогов для данного регистра. Физически в этот момент СУБД очистит таблицы итогов регистра (например, таблицы _AccTtl или _AccumRegTotals) и удалит их индексы.
  6. Попробуйте провести проблемный документ. Поскольку итоги отключены, движения запишутся напрямую в основную таблицу регистра, где проверки уникальности итогов в данный момент не выполняются. Документ должен успешно провестись.
  7. Вернитесь в форму управления итогами, установите актуальный период расчета и снова нажмите кнопку Включить использование итогов. Платформа в фоновом режиме пересчитает все агрегированные таблицы «с нуля» на основе корректных движений, создав правильные индексы без дубликатов.

Метод 2. Диагностика и исправление дублей через SQL-запросы

Если база данных имеет внушительный объем и выгрузка в файловый вариант (формат .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. Зафиксируйте значения ключевых полей (ссылку на регистратор, период и номер строки), которые вызывают двоение.
  2. Если дублирующие строки абсолютно идентичны (например, из-за сбоя транзакции запись выполнилась дважды), нам нужно физически удалить одну из копий. Для этого в SQL временно удаляют (DROP) проблемный уникальный индекс.
  3. Выполняют физическое удаление дублирующихся записей, оставляя только одну уникальную строку.
  4. Важное правило платформы: после ручного удаления уникального индекса в SQL запускать обычную операцию реиндексации в ТиИ бесполезно — платформа не создаст удаленный на уровне СУБД уникальный индекс автоматически. Вместо этого в Тестировании и Исправлении необходимо выбрать пункт «Реструктуризация таблиц». Платформа полностью пересоздаст физическую структуру таблиц СУБД и заново инициализирует уникальный индекс уже на чистых данных.

Метод 3. Программный контроль уникальности при переносе и свертке данных

Если проблема вызвана некорректно созданными ссылками на этапе программного переноса данных, нам необходимо предотвратить появление дубликатов на уровне встроенного языка 1С. Рассмотрим пример правильного назначения ссылки новому документу, чтобы избежать конфликтов уникальности индексов в СУБД.

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


НовыйДокумент = Документы.ПоступлениеТоваровУслуг.СоздатьДокумент();
НовыйДокумент.Дата = ТекущаяДата();

// Генерируем новый уникальный идентификатор (GUID)
НовыйИдентификатор = Новый УникальныйИдентификатор();
НоваяСсылка = Документы.ПоступлениеТоваровУслуг.ПолучитьСсылку(НовыйИдентификатор);

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

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


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

Использование метода ПривестиЗначение() позволяет гарантировать, что в физическую таблицу СУБД запишется корректная типизированная пустая ссылка (например, пустая ссылка справочника «Контрагенты»), а не значение неопределенного типа или системный NULL, который при группировке записей СУБД вызовет ошибку уникального индекса и прервет транзакцию проведения документов.

Резюме и рекомендации

Для предотвращения подобных сбоев при проведении крупных технических работ по свертке баз данных придерживайтесь следующих правил:

← На главную