Как использовать свойство ВерсияДанных для сравнения версий объектов при интеграции и многопоточной загрузке

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

При построении сложных систем интеграции между различными базами данных «1С:Предприятие» часто возникает задача контроля актуальности данных. Особую остроту эта проблема приобретает при использовании многопоточной загрузки. В такой ситуации возможна ситуация «гонки», когда более позднее изменение объекта в источнике (условно, версия 10) может начать обрабатываться одним потоком, в то время как другой поток все еще обрабатывает предыдущее изменение (версию 9). Если поток с версией 9 завершит запись позже, актуальные данные будут затерты устаревшими.

Для решения этой проблемы в платформе 1С предусмотрено системное свойство ВерсияДанных. Разберем подробно, как с ним работать, какие существуют подводные камни и как правильно реализовать алгоритм сравнения версий в коде.

Что такое ВерсияДанных и как она работает

Прежде всего проанализируем природу этого свойства. ВерсияДанных — это скрытое системное поле, которое присутствует у большинства объектных типов (Справочники, Документы, Планы видов характеристик и др.). На уровне СУБД (например, MS SQL Server) этому полю соответствует тип данных rowversion (или timestamp в старых версиях).

Важные особенности механизма:

  1. Инкрементальность: Это поле представляет собой глобальный счетчик в рамках всей базы данных. При любом изменении (создании или обновлении записи) счетчик увеличивается.
  2. Автоматизм: Платформа 1С и СУБД сами управляют этим значением. Программно записать свое значение в ВерсияДанных невозможно.
  3. Тип данных: В 1С это свойство представлено в виде строки формата Base64, которая скрывает под собой 8-байтовое (64-битное) число.

Рассмотрим ситуацию: при передаче объекта из базы-источника в базу-приемник мы передаем и его ВерсияДанных. В приемнике мы сохраняем это значение в дополнительном реквизите (например, ВерсияДанныхИсточника). При следующей загрузке мы можем сравнить: если пришедшая версия больше той, что уже сохранена, — данные актуальны и их нужно записывать.

Почему нельзя сравнивать Base64 напрямую

Разберем типичную ошибку начинающих разработчиков. Кажется логичным просто сравнить две строки Base64. Однако, как отмечают участники обсуждения, это приведет к некорректным результатам. Алфавит кодировки Base64 включает символы ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/. Порядок этих символов не соответствует ни алфавитному порядку строк, ни числовому порядку значений, которые они кодируют.

Следовательно, для корректного сравнения нам необходимо преобразовать ВерсияДанных либо в число, либо в шестнадцатеричную строку (Hex), либо работать напрямую с двоичными данными.

Решение 1: Преобразование версии в число (универсальный метод)

Проанализируем первый способ — написание функции, которая переводит строковое представление 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-битными данными теоретически можно столкнуться с пределом точности. Поэтому рассмотрим более современные и быстрые методы.

Решение 2: Оптимизированный способ через ЧтениеДанных

Начиная с версии платформы 8.3.10, появились мощные инструменты для работы с бинарными данными. Рассмотрим, как выполнить преобразование ВерсияДанных гораздо быстрее, используя объект ЧтениеДанных.


Функция ПолучитьЧисло64ИзВерсии(ВерсияBase64)
    // Преобразуем строку Base64 в двоичные данные
    ДвоичныеДанные = Base64Значение(ВерсияBase64);
    
    // Используем ЧтениеДанных для извлечения 64-битного целого
    Чтение = Новый ЧтениеДанных(ДвоичныеДанные);
    
    // Версия данных в SQL хранится в формате BigEndian (старший байт первый)
    Число64 = Чтение.ПрочитатьЦелое64(ПорядокБайтов.BigEndian);
    
    Возврат Число64;
КонецФункции

Этот метод предпочтительнее, так как он не использует циклы и строковые манипуляции, что значительно повышает производительность при массовой загрузке данных.

Решение 3: Самый быстрый способ (через Hex-строку)

Если ваша конфигурация работает на актуальных релизах платформы (8.3.14+), можно воспользоваться встроенной функцией ЧислоИзШестнадцатеричнойСтроки. Это самый лаконичный способ реализации задачи.


ВерсияСтрокой = Ссылка.ВерсияДанных;
// Формируем Hex-строку с префиксом 0x для корректной работы функции
СтрокаHEX = "0x" + СтрЗаменить(Строка(Base64Значение(ВерсияСтрокой)), " ", ""); 
Число64 = ЧислоИзШестнадцатеричнойСтроки(СтрокаHEX);

Применение при многопоточной загрузке

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

  1. Извлекаем ПришедшаяВерсияЧисло из пакета данных источника.
  2. Находим в приемнике существующий объект и считываем ранее сохраненное значение СохраненнаяВерсияЧисло (из реквизита или регистра сведений).
  3. Выполняем сравнение:

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

Проанализируем ситуацию с блокировками: При работе в несколько потоков крайне важно, чтобы проверка версии и запись происходили внутри одной транзакции с установкой исключительной блокировки на объект. В противном случае два потока могут одновременно считать одну и ту же "старую" версию из базы, оба решат, что их данные "новые", и перезапишут друг друга.

Ограничения и подводные камни

Разберем важные нюансы, которые необходимо учитывать при использовании этого механизма:

Подводя итог, можно сказать, что использование ВерсияДанных — это надежный и производительный способ гарантировать целостность данных при синхронизации. Мы рекомендуем использовать метод с ЧтениеДанных или ЧислоИзШестнадцатеричнойСтроки как наиболее современные и эффективные подходы.

← На главную