При построении сложных систем интеграции между различными базами данных «1С:Предприятие» часто возникает задача контроля актуальности данных. Особую остроту эта проблема приобретает при использовании многопоточной загрузки. В такой ситуации возможна ситуация «гонки», когда более позднее изменение объекта в источнике (условно, версия 10) может начать обрабатываться одним потоком, в то время как другой поток все еще обрабатывает предыдущее изменение (версию 9). Если поток с версией 9 завершит запись позже, актуальные данные будут затерты устаревшими.
Для решения этой проблемы в платформе 1С предусмотрено системное свойство ВерсияДанных. Разберем подробно, как с ним работать, какие существуют подводные камни и как правильно реализовать алгоритм сравнения версий в коде.
Прежде всего проанализируем природу этого свойства. ВерсияДанных — это скрытое системное поле, которое присутствует у большинства объектных типов (Справочники, Документы, Планы видов характеристик и др.). На уровне СУБД (например, MS SQL Server) этому полю соответствует тип данных rowversion (или timestamp в старых версиях).
Важные особенности механизма:
ВерсияДанных невозможно.Рассмотрим ситуацию: при передаче объекта из базы-источника в базу-приемник мы передаем и его ВерсияДанных. В приемнике мы сохраняем это значение в дополнительном реквизите (например, ВерсияДанныхИсточника). При следующей загрузке мы можем сравнить: если пришедшая версия больше той, что уже сохранена, — данные актуальны и их нужно записывать.
Разберем типичную ошибку начинающих разработчиков. Кажется логичным просто сравнить две строки Base64. Однако, как отмечают участники обсуждения, это приведет к некорректным результатам. Алфавит кодировки Base64 включает символы ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/. Порядок этих символов не соответствует ни алфавитному порядку строк, ни числовому порядку значений, которые они кодируют.
Следовательно, для корректного сравнения нам необходимо преобразовать ВерсияДанных либо в число, либо в шестнадцатеричную строку (Hex), либо работать напрямую с двоичными данными.
Проанализируем первый способ — написание функции, которая переводит строковое представление Base64 в десятичное число. Это позволяет использовать стандартные операторы сравнения > и <.
Ниже представлен пример функции, реализующей такой переход через промежуточное преобразование в Hex:
&НаСервере
Функция ВерсияДанныхВЧисло(Ссылка) Экспорт
// Получаем двоичные данные из строки Base64
ДвоичныеДанныеВерсии = Base64Значение(Ссылка.ВерсияДанных);
// Получаем строковое представление (например, "00 00 00 00 00 00 46 7C")
СтрокаHex = СтрЗаменить(Строка(ДвоичныеДанныеВерсии), " ", "");
Результат = 0;
ДлинаСтроки = СтрДлина(СтрокаHex);
// Алгоритм перевода из 16-ричной системы в 10-ричную
Для НомерСимвола = 1 По ДлинаСтроки Цикл
СимволHex = Сред(СтрокаHex, НомерСимвола, 1);
ЧислоDec = Найти("0123456789ABCDEF", ВРег(СимволHex)) - 1;
Результат = Результат + ЧислоDec * Pow(16, ДлинаСтроки - НомерСимвола);
КонецЦикла;
Возврат Результат;
КонецФункции
Важное замечание: Тип Число в 1С поддерживает очень большие значения, однако при работе с 64-битными данными теоретически можно столкнуться с пределом точности. Поэтому рассмотрим более современные и быстрые методы.
Начиная с версии платформы 8.3.10, появились мощные инструменты для работы с бинарными данными. Рассмотрим, как выполнить преобразование ВерсияДанных гораздо быстрее, используя объект ЧтениеДанных.
Функция ПолучитьЧисло64ИзВерсии(ВерсияBase64)
// Преобразуем строку Base64 в двоичные данные
ДвоичныеДанные = Base64Значение(ВерсияBase64);
// Используем ЧтениеДанных для извлечения 64-битного целого
Чтение = Новый ЧтениеДанных(ДвоичныеДанные);
// Версия данных в SQL хранится в формате BigEndian (старший байт первый)
Число64 = Чтение.ПрочитатьЦелое64(ПорядокБайтов.BigEndian);
Возврат Число64;
КонецФункции
Этот метод предпочтительнее, так как он не использует циклы и строковые манипуляции, что значительно повышает производительность при массовой загрузке данных.
Если ваша конфигурация работает на актуальных релизах платформы (8.3.14+), можно воспользоваться встроенной функцией ЧислоИзШестнадцатеричнойСтроки. Это самый лаконичный способ реализации задачи.
ВерсияСтрокой = Ссылка.ВерсияДанных;
// Формируем Hex-строку с префиксом 0x для корректной работы функции
СтрокаHEX = "0x" + СтрЗаменить(Строка(Base64Значение(ВерсияСтрокой)), " ", "");
Число64 = ЧислоИзШестнадцатеричнойСтроки(СтрокаHEX);
Выясним, как интегрировать эти функции в процесс загрузки. Когда данные приходят из внешней системы (источника), мы должны выполнить следующую проверку перед записью объекта в приемнике:
ПришедшаяВерсияЧисло из пакета данных источника.СохраненнаяВерсияЧисло (из реквизита или регистра сведений).
Если ПришедшаяВерсияЧисло > СохраненнаяВерсияЧисло Тогда
// Объект в источнике новее, чем тот, что мы уже загружали
ОбъектПриемник = СсылкаПриемник.ПолучитьОбъект();
// Обновляем данные...
ОбъектПриемник.ДополнительныеСвойства.Вставить("ВерсияДанныхИсточника", ПришедшаяВерсияЧисло);
ОбъектПриемник.Записать();
Иначе
// Это старая версия данных, которая пришла позже актуальной из-за задержки в потоке
// Пропускаем запись
КонецЕсли;
Проанализируем ситуацию с блокировками: При работе в несколько потоков крайне важно, чтобы проверка версии и запись происходили внутри одной транзакции с установкой исключительной блокировки на объект. В противном случае два потока могут одновременно считать одну и ту же "старую" версию из базы, оба решат, что их данные "новые", и перезапишут друг друга.
Разберем важные нюансы, которые необходимо учитывать при использовании этого механизма:
ВерсияДанных. Для них придется использовать другие методы контроля (например, хранить МоментВремени или внедрять собственные счетчики).GUID, его ВерсияДанных начнется с нового текущего значения счетчика СУБД. Обычно это значение будет больше предыдущего, но в редких случаях переноса таблиц или очистки базы счетчик может сброситься.ВерсияДанных для интеграции с механизмом оптимистической блокировки самой платформы 1С. Платформа всегда проверяет версию при вызове метода Записать(). Если другой пользователь изменил объект, пока вы редактировали его в памяти, 1С выдаст ошибку. Наша же задача — сравнить версии из разных информационных баз.ВерсияДанных является пустой строкой. Обязательно добавьте проверку на пустую строку перед вызовом функций преобразования, чтобы избежать исключений.Подводя итог, можно сказать, что использование ВерсияДанных — это надежный и производительный способ гарантировать целостность данных при синхронизации. Мы рекомендуем использовать метод с ЧтениеДанных или ЧислоИзШестнадцатеричнойСтроки как наиболее современные и эффективные подходы.