Как надежно заблокировать регистр сведений для записи в 1С:Предприятие?

Программист 1С v8.3 (Управляемые формы) 1С:Управление торговлей Торговля и дистрибуция
← К списку

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

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

Вариант 1: Блокировка данных для редактирования в управляемой форме (ЗаблокироватьДанныеДляРедактирования())

Начнем с рассмотрения механизма, который часто приходит на ум при необходимости блокировки, но имеет свои специфические особенности применения: процедура ЗаблокироватьДанныеДляРедактирования().

  1. Назначение процедуры

    Мы используем процедуру ЗаблокироватьДанныеДляРедактирования() в основном для пессимистической блокировки данных в управляемых формах клиентского приложения. Ее главная цель — предотвратить одновременное редактирование одной и той же записи разными пользователями через формы. Это помогает избежать дублирования или перезаписи данных, когда несколько пользователей работают с одной и той же формой.

  2. Область действия блокировки

    Эта блокировка устанавливается на время жизни формы, если мы указываем ИдентификаторФормы. Если же ИдентификаторФормы не указан, блокировка является временной и снимается при завершении транзакции, возврате управления с сервера или завершении сеанса.

  3. Ключевое ограничение

    Здесь мы должны обратить особое внимание на важный нюанс: ЗаблокироватьДанныеДляРедактирования() блокирует данные только для редактирования в управляемой форме. Она не предотвращает программное чтение или запись этих данных другими серверными процессами или методами, которые не используют формы. То есть, если мы хотим обеспечить надежную блокировку на уровне кода, этот метод нам не подойдет.

  4. Пример использования (концептуальный)

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

    Например, в модуле формы объекта:

    
    &НаСервере
    Процедура ПриСозданииНаСервере(Отказ, СтандартнаяОбработка)
        Если Объект.ЭтоНовый Тогда
            Возврат;
        КонецЕсли;
    
        // Блокируем данные для редактирования другими пользователями
        // Объект - это, например, СправочникОбъект.Номенклатура
        // или РегистрСведенийЗапись.МойРегистрСведений
        ЗаблокироватьДанныеДляРедактирования(Объект.Ссылка, УникальныйИдентификатор); 
    КонецПроцедуры
    

    Где УникальныйИдентификатор — это идентификатор текущей формы (например, УникальныйИдентификаторФормы).

Вариант 2: Программная блокировка с использованием объекта БлокировкаДанных

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

  1. Контекст использования: Транзакции и управляемые блокировки

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

    Режим управления блокировками определяется транзакцией самого верхнего уровня. Если уже существует транзакция в управляемом режиме, то начинаемая вложенная транзакция также должна быть в управляемом режиме, иначе возникнет исключительная ситуация.

  2. Шаги по установке блокировки с БлокировкаДанных

    Давайте разберем по шагам, как мы можем установить программную блокировку:

    1. Создание объекта БлокировкаДанных
      Мы начинаем с создания нового экземпляра объекта БлокировкаДанных:

      
      Блокировка = Новый БлокировкаДанных;
      
    2. Добавление элементов блокировки
      Затем мы добавляем элементы блокировки, указывая, какие данные мы хотим заблокировать. Для регистра сведений мы указываем полное имя регистра.

      
      ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.МойРегистрСведений");
      
    3. Установка режима блокировки
      Мы обязательно устанавливаем режим блокировки. Чаще всего для записи нам нужен РежимБлокировкиДанных.Исключительный, который гарантирует, что никто другой не сможет ни читать, ни записывать заблокированные данные. Существует также РежимБлокировкиДанных.РазрешениеЧтения, который позволяет другим сеансам читать данные, но запрещает запись.

      
      ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
      
    4. Указание области блокировки (отбор)
      Для регистров сведений крайне важно указать, какие конкретные записи мы блокируем. Это делается путем установки значений по измерениям и/или стандартному полю "Период" (если регистр периодический). Если мы не укажем отбор, будет заблокирован весь регистр, что может привести к снижению производительности.

      
      // Блокируем конкретную запись по измерениям
      ЭлементБлокировки.УстановитьЗначение("Измерение1", МоеЗначениеИзмерения1);
      ЭлементБлокировки.УстановитьЗначение("Измерение2", МоеЗначениеИзмерения2);
      // Если регистр периодический, можно указать период:
      // ЭлементБлокировки.УстановитьЗначение("Период", ДатаПериода);
      
    5. Установка блокировки
      Наконец, мы вызываем метод Заблокировать(). Этот метод будет ожидать снятия блокировки другими сеансами или вызовет исключение по истечении времени ожидания, если данные уже заблокированы.

      
      Блокировка.Заблокировать();
      
  3. Полный пример кода с транзакцией

    Теперь давайте посмотрим на пример, который объединяет все вышеописанные шаги в контексте транзакции. Мы обязательно используем блок Попытка...Исключение...КонецПопытки для корректной обработки ошибок и отката транзакции.

    
    НачатьТранзакцию();
    Попытка
        // 1. Создаем объект блокировки
        Блокировка = Новый БлокировкаДанных;
    
        // 2. Добавляем элемент блокировки для нашего регистра сведений
        ЭлементБлокировки = Блокировка.Добавить("РегистрСведений.МойРегистрСведений");
    
        // 3. Устанавливаем режим блокировки - исключительный для записи
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
    
        // 4. Указываем область блокировки по измерениям (пример)
        // Предположим, мы хотим заблокировать записи, где Измерение1 равно "Значение1"
        ЭлементБлокировки.УстановитьЗначение("Измерение1", МоеЗначениеИзмерения1);
        // Если необходимо заблокировать по нескольким измерениям, добавляем их:
        // ЭлементБлокировки.УстановитьЗначение("Измерение2", МоеЗначениеИзмерения2);
    
        // 5. Устанавливаем блокировку. Система будет ждать, если данные уже заблокированы.
        Блокировка.Заблокировать();
    
        // --- Здесь выполняем действия по чтению и записи данных ---
        // Важно: для чтения данных, которые должны учитывать блокировку,
        // используем объектное чтение (НаборЗаписей.Прочитать()), а не запросы!
        НаборЗаписей = РегистрыСведений.МойРегистрСведений.СоздатьНаборЗаписей();
        НаборЗаписей.Отбор.Измерение1.Установить(МоеЗначениеИзмерения1);
        // Если есть другие измерения в отборе, добавляем их:
        // НаборЗаписей.Отбор.Измерение2.Установить(МоеЗначениеИзмерения2);
            
        НаборЗаписей.Прочитать(); // Это чтение учтет управляемые блокировки
    
        // Модифицируем набор записей или добавляем новые записи
        // Например, если хотим изменить существующую запись:
        Если НаборЗаписей.Количество() > 0 Тогда
            Запись = НаборЗаписей[0];
            Запись.Ресурс1 = Запись.Ресурс1 + 1;
        Иначе
            // Добавляем новую запись, если не нашли
            НоваяЗапись = НаборЗаписей.Добавить();
            НоваяЗапись.Измерение1 = МоеЗначениеИзмерения1;
            // ... заполняем остальные измерения и ресурсы
        КонецЕсли;
    
        НаборЗаписей.Записать(); // Запись данных
    
        // --- Конец действий ---
    
        // 6. Фиксируем транзакцию, если все прошло успешно
        ЗафиксироватьТранзакцию();
    
    Исключение
        // 7. Отменяем транзакцию в случае ошибки
        ОтменитьТранзакцию();
        // 8. Очень важно перевыбросить исключение, чтобы вышестоящий код узнал об ошибке
        ВызватьИсключение;
    КонецПопытки;
    
  4. Особенности чтения данных в транзакции с блокировками

    Давайте проанализируем ситуацию с чтением данных. Мы должны четко понимать разницу:

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

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

    • В то же время, объектное чтение (например, НаборЗаписей.Прочитать()) учитывает управляемые блокировки. Поэтому, если нам нужно прочитать данные, которые мы только что заблокировали, используйте объектные методы чтения.

Общие рекомендации и лучшие практики

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

  1. Избегайте записи наборов записей в цикле

    Мы настоятельно рекомендуем избегать записи наборов записей регистров сведений в цикле по одной или нескольким записям. Это значительно медленнее и увеличивает объем журнала транзакций СУБД по сравнению с записью регистра одним набором. Всегда старайтесь собрать все изменения в один НаборЗаписей и записать его однократно.

  2. Контроль за количеством блокировок и эскалация

    При большом количестве управляемых блокировок (более 100 000 на одно пространство) может произойти эскалация блокировки. Это ситуация, когда система вместо множества мелких блокировок блокирует все пространство данных (например, весь регистр или даже всю таблицу), что значительно снижает параллельность работы и может привести к "зависанию" системы для других пользователей.

  3. Корректная обработка исключений в транзакциях

    Как мы уже видели в примере, крайне важно правильно обрабатывать исключения в транзакциях. Если в транзакции произошла ошибка, ее следует отменить (`ОтменитьТранзакцию()`) и выбросить исключение (`ВызватьИсключение`), а не пытаться зафиксировать или продолжить. Неправильная обработка может привести к блокировке ресурсов, последующему откату всех изменений и непредсказуемому поведению системы.

  4. Минимизация времени транзакций и блокировок

    Мы стремимся к тому, чтобы транзакции и, соответственно, блокировки были как можно короче. Чем дольше удерживаются блокировки, тем выше вероятность возникновения конкурентных конфликтов и снижения производительности. Выполняйте в транзакции только те действия, которые действительно требуют атомарности и целостности.

Надеемся, что этот подробный разбор поможет вам эффективно использовать механизмы блокировки регистров сведений в 1С:Предприятии и создавать более надежные и производительные решения!

← К списку