При интеграции различных информационных систем на платформе 1С:Предприятие разработчики часто сталкиваются с необходимостью связать документы в двух или более абсолютно независимых базах данных. Одной из классических проблем является ограничение длины полей, выделенных под хранение уникальных идентификаторов. Например, в конфигурации-приемнике уже существует неиспользуемый текстовый реквизит длиной всего 9 символов, и возникает соблазн использовать именно его для связи объектов.
В этой статье мы подробно разберем, почему попытка усечь стандартный 128-битный GUID (UUID) до короткой строки в 8–9 символов таит в себе серьезные математические и финансовые риски, особенно если речь идет о платежных или складских документах. Мы детально проанализируем альтернативные способы генерации уникальных кодов, рассмотрим математику коллизий хэш-функций и разберем профессиональные архитектурные подходы 1С, которые позволяют связать документы вообще без изменения метаданных конфигурации.
Для начала проанализируем природу уникального идентификатора. Стандартный УникальныйИдентификатор в 1С представляет собой 128-битное число (16 байт). В текстовом шестнадцатеричном представлении он записывается как строка из 36 символов (например, 550e8400-e29b-41d4-a716-446655440000).
Попробуем сопоставить объем информации в исходном GUID и текстовом реквизите фиксированной длины:
Разберем ситуацию, когда разработчик пытается получить 9-символьный код путем взятия части хэша от GUID или номера документа (например, используя алгоритмы CRC32, MD5 или SHA-1). Хэш CRC32 дает нам 32-битное значение, которое укладывается в 8 шестнадцатеричных символов. Хэширование по алгоритму MD5 с последующим усечением до 9 символов дает нам 36 бит энтропии.
Рассмотрим подробнее риски коллизий, используя теорию вероятностей и так называемый парадокс дней рождения. Вероятность того, что в базе данных не произойдет ни одного совпадения кодов при накоплении определенного количества документов, стремительно падает с ростом объема базы.
Для 36-битного пространства уникальных кодов (усеченный до 9 символов хэш) вероятность коллизии становится математически значимой (более 1%) уже при наличии в базе всего 260 000 документов. Если ваша система обрабатывает сотни документов в день, то через пару лет вы обязательно столкнетесь с ситуацией, когда два абсолютно разных платежных поручения из первой базы получат один и тот же "уникальный" 9-значный код во второй базе. Последствия такой ошибки в финансовых системах могут быть катастрофическими: перекрестная привязка оплат, закрытие не тех задолженностей и искажение взаиморасчетов.
Если бизнес-требования жестко настаивают на наличии короткого уникального идентификатора длиной до 9 символов и стандартный GUID использовать нельзя, рассмотрим вариант создания системного последовательного нумератора внутри самой 1С.
Для этого мы можем создать служебный пустой справочник (назовем его СлужебныеНомера) со следующими настройками:
Каждый раз при создании нового документа в первой базе мы будем программно создавать новый элемент этого справочника, получать его уникальный последовательный код и записывать его в наш 9-символьный реквизит документа. Посмотрим на пример кода:
Функция ПолучитьНовыйУникальныйКод9() Экспорт
УстановитьПривилегированныйРежим(Истина);
НовыйЭлемент = Справочники.СлужебныеНомера.СоздатьЭлемент();
НовыйЭлемент.Наименование = "Служебный код для обмена";
Попытка
НовыйЭлемент.Записать();
Возврат СокрЛП(НовыйЭлемент.Код);
Исключение
ЗаписьЖурналаРегистрации("Интеграция.ГенерацияКода",
УровеньЖурналаРегистрации.Ошибка,,,
ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
ВызватьИсключение "Не удалось сгенерировать уникальный код для документа!";
КонецПопытки;
КонецФункции
Важное предупреждение: При работе в высоконагруженных системах (HighLoad) со множеством одновременно работающих пользователей стандартные механизмы автонумерации платформы 1С могут давать сбои. При высокой частоте транзакций СУБД (например, MS SQL Server) под управлением менеджера кластера 1С иногда может выдать одинаковые номера разным транзакциям до момента фиксации записи. Чтобы избежать коллизий в критически важных узлах, генерацию уникальных последовательностей рекомендуется выносить на уровень СУБД (используя механизмы SEQUENCE в MS SQL или PostgreSQL) или использовать управляемые блокировки в 1С.
Если обе базы данных построены на современных типовых конфигурациях 1С (например, "1С:Управление торговлей", "1С:ERP" или "1С:Бухгалтерия"), в них почти наверняка интегрирована Библиотека Стандартных Подсистем (БСП). В составе БСП есть готовый, оптимизированный и надежный инструмент для решения подобных задач — независимый регистр сведений СоответствияОбъектовИнформационныхБаз.
Этот регистр специально спроектирован для того, чтобы сопоставлять объекты текущей базы с объектами внешних систем. Использование этого механизма избавляет разработчика от необходимости заводить новые реквизиты в документах или пытаться урезать уникальные идентификаторы.
Проанализируем структуру этого регистра:
Рассмотрим подробнее, как программно работать с этим регистром при загрузке документа из внешней базы:
Процедура ЗаписатьСоответствиеОбъектов(СобственнаяСсылка, ИдентификаторИсточника, УзелОбмена) Экспорт
МенеджерЗаписи = РегистрыСведений.СоответствияОбъектовИнформационныхБаз.СоздатьМенеджерЗаписи();
МенеджерЗаписи.УзелИнформационнойБазы = УзелОбмена;
МенеджерЗаписи.СобственнаяСсылка = СобственнаяСсылка;
// Заполняем ключевые измерения для поиска
МенеджерЗаписи.УникальныйИдентификаторИсточника = ИдентификаторИсточника;
МенеджерЗаписи.УникальныйИдентификаторПриемника = XMLСтрока(СобственнаяСсылка);
МенеджерЗаписи.ТипИсточника = "ДокументСсылка.Оплата";
МенеджерЗаписи.ТипПриемника = "ДокументСсылка.ПоступлениеБезналичныхДенежныхСредств";
Попытка
МенеджерЗаписи.Записать(Истина);
Исключение
ВызватьИсключение "Ошибка записи соответствия объектов в регистр БСП: " + ОписаниеОшибки();
КонецПопытки;
КонецПроцедуры
При получении данных из базы-источника мы сначала проверяем наличие записи в этом регистре. Если запись найдена, мы получаем ссылку на уже существующий документ и обновляем его. Если записи нет — создаем новый документ и регистрируем связь в регистре. Это эталонный архитектурный подход, гарантирующий абсолютную надежность обмена без риска появления дублей.
Самым простым, изящным и производительным решением задачи синхронизации документов является создание документа в базе-приемнике с точно таким же уникальным идентификатором (GUID), какой был у документа в базе-источнике.
Платформа 1С позволяет жестко задать GUID создаваемого объекта до его первой записи в базу данных с помощью метода УстановитьСсылкуНового(). Разберем этот процесс по шагам.
Представим, что база-источник передала нам XML или JSON-пакет, в котором содержится GUID исходного документа в виде строки: "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d".
Выполним следующий алгоритм в базе-приемнике:
Функция СоздатьДокументСИдентичнымGUID(СтрокаGUID) Экспорт
// 1. Преобразуем строку в системный тип УникальныйИдентификатор
НовыйУИД = Новый УникальныйИдентификатор(СтрокаGUID);
// 2. Получаем ссылку на документ в текущей базе по этому GUID
СсылкаНаДокумент = Документы.ПоступлениеБезналичныхДенежныхСредств.ПолучитьСсылку(НовыйУИД);
// 3. Проверяем, существует ли уже физически такой документ в базе
// Для этого используем попытку получения реквизита "Ссылка" через обращение к БД
ДокументСуществует = (СсылкаНаДокумент.ВерсияДанных <> "");
Если ДокументСуществует Тогда
// Документ уже был загружен ранее, получим объект для обновления
ОбъектДокумента = СсылкаНаДокумент.ПолучитьОбъект();
ЗаписьЖурналаРегистрации("Интеграция.Обновление", УровеньЖурналаРегистрации.Информация,,, "Обновление существующего документа");
Иначе
// Документа нет, создаем новый
ОбъектДокумента = Документы.ПоступлениеБезналичныхДенежныхСредств.СоздатьДокумент();
// КРИТИЧЕСКИ ВАЖНО: устанавливаем подготовленную ссылку новому объекту до записи
ОбъектДокумента.УстановитьСсылкуНового(СсылкаНаДокумент);
ЗаписьЖурналаРегистрации("Интеграция.Создание", УровеньЖурналаРегистрации.Информация,,, "Создание нового документа с внешним GUID");
КонецЕсли;
// 4. Заполняем реквизиты документа полученными данными
ОбъектДокумента.Дата = ТекущаяДата();
// ... заполнение остальных реквизитов ...
// 5. Записываем объект
Попытка
ОбъектДокумента.Записать(РежимЗаписиДокумента.Проведение);
Возврат ОбъектДокумента.Ссылка;
Исключение
ЗаписьЖурналаРегистрации("Интеграция.ОшибкаЗаписи", УровеньЖурналаРегистрации.Ошибка,,, ПодробноеПредставлениеОшибки(ИнформацияОбОшибке()));
Возврат Неопределено;
КонецПопытки;
КонецФункции
Преимущества этого подхода очевидны:
В старых версиях платформы 1С получение текстового представления GUID в запросах было нетривиальной задачей. Разработчикам приходилось выгружать выборку в память и использовать ресурсоемкие функции вроде XMLСтрока(Ссылка) в цикле.
В современных релизах платформы (начиная с версии 8.3.22) появилась штатная функция языка запросов УНИКАЛЬНЫЙИДЕНТИФИКАТОР(). Теперь мы можем мгновенно получать строковые представления GUID прямо из СУБД, что значительно упрощает сопоставление и выгрузку данных.
Проанализируем пример такого запроса:
Запрос = Новый Запрос;
Запрос.Текст =
"ВЫБРАТЬ
| ПлатежноеПоручение.Ссылка КАК Ссылка,
| ПлатежноеПоручение.Номер КАК Номер,
| ПлатежноеПоручение.Дата КАК Дата,
| УНИКАЛЬНЫЙИДЕНТИФИКАТОР(ПлатежноеПоручение.Ссылка) КАК СтрокаGUID
|ИЗ
| Документ.ПлатежноеПоручение КАК ПлатежноеПоручение
|ГДЕ
| ПлатежноеПоручение.Проведен = ИСТИНА";
РезультатЗапроса = Запрос.Выполнить();
Используя этот механизм в сочетании с методом УстановитьСсылкуНового(), вы сможете построить невероятно быструю, легкую и, самое главное, надежную систему обмена данными без внесения "костылей" в виде усеченных строк и сомнительных хэш-функций.
Подведем итоги нашего анализа. Если перед вами стоит задача связать документы в разных базах 1С, придерживайтесь следующих правил:
УстановитьСсылкуНового().СоответствияОбъектовИнформационныхБаз. Это сохранит чистоту метаданных ваших документов.