Как работает и меняется реквизит «ВерсияДанных» в 1С:Предприятии?

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

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

Глубокое погружение в структуру реквизита «ВерсияДанных»

Для начала, давайте проясним, что именно представляет собой реквизит ВерсияДанных. В контексте платформы 1С он всегда представлен в виде строки, закодированной по алгоритму Base64. Примером такого значения может служить "AAAAXwAAAAA=". Выбор Base64 кодирования не случаен: он позволяет компактно и безопасно хранить бинарные данные в текстовом виде, что удобно для передачи по различным каналам и хранения в текстовых полях.

Чтобы раскрыть истинную природу этого реквизита, мы должны применить обратную функцию Base64Значение. Эта функция декодирует Base64-строку обратно в двоичные данные. После декодирования мы получаем 8-байтовый массив, который фактически представляет собой 64-битное целое число. Если мы переведем эти двоичные данные в шестнадцатеричное представление, например, для нашей строки "AAAAXwAAAAA=", мы увидим что-то вроде "00 00 00 5F 00 00 00 00".

Ключевым аспектом функционирования ВерсияДанных является его роль как возрастающего счетчика записи. Этот счетчик автоматически инкрементируется платформой каждый раз, когда объект записывается в информационную базу. Этот простой, но эффективный механизм позволяет нам моментально определить, был ли объект изменен с момента его последнего чтения или сохранения. Представьте ситуацию: если при первой записи объекта значение реквизита было "00 00 00 5F 00 00 00 00", то при последующей модификации и сохранении того же объекта оно преобразуется в "00 00 00 60 00 00 00 00". Очевидно, что изменение всего лишь одной шестнадцатеричной цифры свидетельствует о факте модификации.

Загадка little-endian: почему меняются «первые 8 цифр»?

Один из наиболее часто возникающих вопросов, который изначально поднимал автор темы, заключается в том, почему при инкрементировании реквизита ВерсияДанных изменения затрагивают только "первые 8 цифр" в шестнадцатеричном представлении (то есть, младшую часть 64-битного числа), а "последние 8 цифр" (старшая часть) кажутся неизменными на протяжении долгого времени. Разгадка кроется в способе хранения многобайтовых данных в памяти компьютера, а именно — в использовании формата little-endian.

Давайте подробнее разберем концепцию little-endian. Этот порядок байтов означает, что младшие байты многобайтового числа хранятся по младшим адресам памяти, а старшие байты — по старшим. Это контрастирует с форматом big-endian, где старшие байты располагаются по младшим адресам. Для нашего 64-битного (8-байтового) целого числа, это имеет следующие последствия при его представлении в шестнадцатеричном виде:

  1. Младшее двойное слово: Первые 4 байта числа (младшее двойное слово, что эквивалентно 32 битам) будут отображаться как первые 8 шестнадцатеричных цифр в строке. Именно эта часть числа чаще всего изменяется при инкременте.
  2. Старшее двойное слово: Следующие 4 байта числа (старшее двойное слово) будут отображаться как последние 8 шестнадцатеричных цифр. Эта часть меняется гораздо реже.

Таким образом, когда мы видим декодированное шестнадцатеричное представление, например, "00 00 00 5F 00 00 00 00":

Механизм инкрементации 64-битного счетчика, как мы видим, построен на классическом принципе работы с числами:

  1. Постепенный инкремент младшего двойного слова: При каждой записи объекта значение счетчика увеличивается на единицу. Это изменение происходит в первую очередь в младшем двойном слове. Именно поэтому вы регулярно наблюдаете изменения "первых 8 цифр" (например, последовательность 5F, затем 60, 61 и так далее).
  2. Переполнение и инкремент старшего двойного слова: Младшее двойное слово (32-битное) имеет свое максимальное значение, которое в шестнадцатеричном формате выражается как FFFFFFFF (что соответствует 232 - 1 или 4 294 967 295 в десятичной системе). Когда младшее двойное слово достигает этого максимума, и происходит очередное инкрементирование, оно "переполняется". В результате этого переполнения младшее двойное слово сбрасывается к своему начальному значению 00000000, а старшее двойное слово (те самые "последние 8 цифр") увеличивается на единицу.

Чтобы проиллюстрировать этот процесс, давайте рассмотрим пример перехода:

Если текущая ВерсияДанных в шестнадцатеричном представлении выглядит как:


FF FF FF FF 00 00 00 00

(где FF FF FF FF представляет младшее двойное слово, а 00 00 00 00 — старшее), то при следующем инкременте значение реквизита изменится на:


00 00 00 00 00 00 00 01

В этом примере мы наглядно видим, как младшее двойное слово обнулилось, а старшее двойное слово стало равно 00 00 00 01. Это подтверждает, что "последние 8 цифр" действительно изменяются, но их изменение происходит крайне редко, поскольку для этого требуется колоссальное количество записей одного и того же объекта — более 4 миллиардов операций. Именно поэтому при обычном использовании системы, а также при операциях загрузки или обмена данными, мы обычно не наблюдаем изменений в старшей части числа, что и приводило к первоначальному недопониманию.

Функциональное назначение и сценарии применения реквизита «ВерсияДанных»

Понимание механизма изменения реквизита ВерсияДанных открывает перед нами его истинное предназначение и потенциал использования в прикладной разработке. Давайте подробно рассмотрим основные сферы его применения:

  1. Реализация оптимистической блокировки. Это одна из ключевых функций, которую может выполнять ВерсияДанных. Оптимистическая блокировка — это подход к управлению параллельным доступом, который предполагает, что конфликты данных возникают редко. Вместо того чтобы блокировать объект для редактирования (пессимистическая блокировка), мы позволяем нескольким пользователям одновременно открывать и потенциально изменять один и тот же объект. Алгоритм выглядит так:
    1. При загрузке объекта для редактирования, мы сохраняем его текущее значение ВерсияДанных.
    2. Пользователь вносит свои изменения.
    3. Перед окончательной записью объекта в базу данных, мы вновь получаем его текущую ВерсиюДанных из БД и сравниваем ее с сохраненной в начале редактирования.
    4. Если версии не совпадают, это означает, что объект был изменен другим пользователем в процессе нашего редактирования. В этой ситуации мы можем предотвратить потерю данных:
      • Вывести сообщение пользователю о конфликте.
      • Предложить обновить данные и повторить редактирование.
      • В более сложных сценариях, попытаться выполнить слияние изменений.
    5. Если версии совпадают, мы безопасно записываем объект, и его ВерсияДанных инкрементируется.

    Этот подход повышает пропускную способность системы, так как объекты не блокируются на длительное время.

  2. Простое определение факта изменения объекта. Самый очевидный и простой сценарий использования — это быстрое определение того, был ли объект модифицирован с момента его последней загрузки или сохранения. Если значение ВерсияДанных, полученное при первом чтении, отличается от значения при повторном чтении, мы однозначно можем утверждать, что объект был изменен. Для этой цели даже не требуется декодировать Base64-строку; достаточно выполнить простое строковое сравнение. Это может быть полезно для обновления данных в пользовательском интерфейсе или для предотвращения отправки "пустых" изменений на сервер.
  3. Разработка собственных алгоритмов конкурентной записи. Для особо специфических задач, когда стандартные механизмы блокировки или версионирования не подходят, разработчик может использовать ВерсияДанных как низкоуровневую основу для создания уникальных алгоритмов конкурентной записи. Например, это может быть необходимо при реализации сложных сценариев объединяющей записи, когда разные части одного и того же объекта редактируются параллельно разными пользователями, и их изменения необходимо интеллектуально слить без полной перезаписи.
  4. Разграничение с механизмом «История данных» (Версионирование объектов). Важно четко понимать разницу между служебным реквизитом ВерсияДанных и механизмом "История данных" (иногда называемым версионированием объектов), который также присутствует в платформе 1С:Предприятие.
    • ВерсияДанных — это внутренний, низкоуровневый системный счетчик, который лишь фиксирует факт изменения объекта и его порядковый номер в цепочке модификаций. Он не содержит информации о том, кто, когда и какие конкретно поля объекта были изменены. Это просто "маркер" актуальности.
    • «История данных» — это высокоуровневый прикладной механизм, который позволяет сохранять полные или частичные копии объектов (или их отдельных реквизитов) при каждом изменении. Этот механизм предоставляет богатую информацию: кто (пользователь), когда (дата и время) и какие именно изменения вносил. Он позволяет просматривать предыдущие состояния объекта и при необходимости восстанавливать их. «История данных» не использует напрямую значение ВерсияДанных для хранения самих исторических данных, но она, безусловно, опирается на факт изменения объекта, для регистрации которого и обновляется ВерсияДанных. Механизм «История данных» может быть настроен для таких объектов конфигурации, как справочники, документы, бизнес-процессы, задачи, регистры сведений и общие реквизиты, предоставляя гибкие возможности для контроля изменений на прикладном уровне.

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

Практические аспекты использования и важные предостережения

После того как мы детально изучили структуру и назначение реквизита ВерсияДанных, важно рассмотреть практические аспекты его применения в реальных проектах на платформе 1С:Предприятие.

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

Однако, когда мы говорим о нетиповых разработках, кастомизированных решениях или интеграциях, ВерсияДанных может стать незаменимым инструментом. Например, при создании собственной оптимистической блокировки, как мы уже обсуждали, код будет выглядеть примерно так:


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

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

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

← На главную