![]()
|
СУБД | ||||
Файловая база данных |
MS SQL Server |
IBM DB2 |
PostgreSQL | |
Вид блокировок |
Таблиц |
Записей |
Записей |
Таблиц |
Уровень изоляции транзакций |
Serializable |
Repeatable Read или Serializable |
Serializable |
Read Committed |
Зачастую такой подход приводит к возникновению «плохих»
(избыточных) блокировок и не позволяет достичь желаемой параллельности
работы пользователей. В клиент-серверном варианте блокировка данных
происходит на уровне записей, однако может быть заблокирована и вся
таблица целиком (например, в результате выбора СУБД неоптимального плана
выполнения запроса ). Тип блокировок, устанавливаемых в том или ином
случае, зависит от вида операции, используемого 1С:Предприятием уровня
изоляции транзакций и определяется внутренними механизмами самой СУБД
(например, MS SQL Server).
В 1С:Предприятии версии 8.1 реализован дополнительный режим работы, позволяющий использовать собственный менеджер транзакционных блокировок 1С:Предприятия, независимый от используемой СУБД (рис. 16).
Рис. 16. Управляемые блокировки в транзакции 1С:Предприятия 8.1
При работе в этом режиме система использует гораздо более низкий уровень изоляции транзакций для MS SQL Server и IBM DB2, и блокировку на уровне записей для PostgreSQL (см. таблицу 3). Это позволяет достичь более высокой параллельности работы пользователей.
Таблица 3. Блокировки СУБД, используемые в режиме управляемых блокировок в транзакции
СУБД | ||||
Файловая база данных |
MS SQL Server |
IBM DB2 |
PostgreSQL | |
Вид блокировок |
Таблиц |
Записей |
Записей |
Записей |
Уровень изоляции транзакций |
Serializable |
Read Committed |
Read Committed |
Read Committed |
Однако этот уровень изоляции транзакций СУБД уже не может сам по себе обеспечить целостность и непротиворечивость данных во всех случаях. Поэтому 1С:Предприятие 8.1 при модификации данных методами встроенного языка (например, метод Записать() у объектных данных) устанавливает собственные управляемые блокировки в транзакции, которые обрабатываются собственным менеджером транзакционных блокировок. Эти блокировки также могут быть установлены и разработчиком самостоятельно в тех местах кода, где требуется обеспечить неизменность считываемых в транзакции данных (разделяемая блокировка) или запретить чтение данных другими транзакциями (исключительная блокировка).
Управляемые блокировки 1С:Предприятия учитывают логическую структуру прикладного решения поэтому позволяют максимально точно блокировать необходимые области данных (в отличие от использовавшихся ранее блокировок СУБД, которым не известна логическая структура системы). Таким образом менеджер управляемых блокировок позовляет максимально избежать возникновения «плохих» (избыточных) блокировок, блокируюя только действительно необходимые области данных.
В результате любой запрос к данным прежде всего обрабатывается собственным менеджером транзакционных блокировок 1С:Предприятия 8.1 (см. рис. 16). Если на уровне 1С:Предприятия 8.1 конфликт управляемых блокировок не обнаруживается, то запрос передается далее, на исполнение СУБД. СУБД также использует собственный механизм блокировок для определения конфликтующих транзакций, но уже с более низким уровнем изоляции транзакций, чем в режиме автоматических блокировок.
В структуре объектов конфигурации существует несколько возможностей для задания режима управления блокировками.
Прежде всего существует свойство Режим управления
блокировкой данных самой конфигурации (рис. 17).
Рис. 17. Список значений свойства «Режим управления блокировкой данных» в палитре свойств Конфигурации
При выборе значений Автоматический или Управляемый режим блокировок при чтении или записи данных любого объекта конфигурации будет определяться именно этим выбранным значением.
Например, если установлен режим Автоматический, то при записи, скажем, любого
элемента справочника, будут использоваться автоматические блокировки,
устанавливаемые СУБД. Собственнный менеджер блокировок задействован не
будет. Поведение системы будет полностью аналогичным поведению версии 8.0.
Если же установлен режим Управляемый, то, независимо от того, какие режимы
управления блокировками установлены для конкретных объектов конфигурации
(об этом смотри далее), при записи, скажем, документа система всегда будет
самостоятельно устанавливать необходимые управляемые блокировки, которые
будут обрабатываться собственным менеджером транзакционных блокировок.
Этот режим предназначен для работы всей конфигурации только с управляемыми
блокировками в транзакции.
Если же для свойства конфигурации выбран режим Автоматический и управляемый, то для конкретного
объекта конфигурации режим блокировки будет определяться значением
свойства Режим управления блокировкой данных
самого объекта конфигурации (рис. 18).
Рис. 18. Список значений свойства «Режим управления блокировкой данных» в палитре свойств объекта конфигурации
Этот режим предназначен для постепенного или частичного перевода конфигурации в режим управляемых блокировок. Он позволяет отдельным объектам метаданных работать с управляемыми блокировками (например, наиболее «проблемным» документам и регистрам), в то время как остальные объекты работают в режиме автоматических блокировок.
Важной особенностью работы в режиме Автоматический и управляемый является то, что не во всех ситуациях работа с данными объекта будет выполняться именно в том режиме, который для него указан. Рассмотрим эту особенность подробно.
Внутри одной транзакции, которая начата и не завершена 1С:Предприятием, может быть начата еще одна (или несколько) транзакций. Такая логика работы обеспечивается платформой автоматически, а также поддерживается средствами встроенного языка.
В 1С:Предприятии 8.1 при начале каждой транзакции явно (если она начата из встроенного языка) или неявно (если она начата в результате действий самой системы) указывается режим управления блокировками в данной транзакции (автоматический или управляемый). Таким образом может оказаться, что первая (объемлющая) транзакция открыта в одном режиме, а вторая - в другом режиме управления блокировками. Всего может быть четыре различных сочетания, которые представлены в таблице 3.
Таблица 3.
Сочетания режимов управления блокировками в транзакции
Режим существующей транзакции |
Режим начинаемой транзакции |
Результат |
Автоматический |
Автоматический |
Начинаемая транзакция будет выполнена в автоматическом режиме |
Управляемый |
Управляемый |
Начинаемая транзакция будет выполнена в управляемом режиме |
Автоматический |
Управляемый |
Начинаемая транзакция будет выполнена в автоматическом режиме |
Управляемый |
Автоматический |
Будет вызвана исключительная ситуация |
Указанная ранее особенность проявляется в последних двух строках таблицы. Если существующая транзакция начата в автоматическом режиме, то начинаемая транзакция таже будет выполнена в автоматическом режиме, даже в том случае, если явно (во втроенном языке) или неявно (в свойствах объекта конфигурации) для нее установлен управляемый режим блокировок.
Если же существующая транзакция начата в управляемом режиме, то начинаемая транзакция может быть выполнена только в том случае, если для нее также указан управляемый режим. Если для нее указан автоматический режим - будет вызвана исключительная ситуация.
Разберем эту особенность на двух примерах.
Например, запись элемента справочника выполняется из встроенного языка внутри транзакции, открытой разработчиком. В этом случае «первой» (явной) транзакцией будет транзакция, инициированная разработчиком, а «второй» (неявной) будет транзакция, открываемая платформой при выполнении метода Записать() объекта справочника.
Явная транзакция открывается разработчиком с помощью метода встроенного языка НачатьТранзакцию(). В отличие от версии 8.0 этот метод имеет параметр БлокировкаДанных, который указывает какой режим управления блокировками будет использоваться в данной транзакции. По умолчанию значение этого параметра равно Автоматический. Поэтому, если разработчик использует значение этого параметра по умолчанию, то независимо от того, какой режим установлен в свойствах записываемого справочника, его запись будет выполнена в автоматическом режиме (см. табл. 3, 1 и 3 строки).
Если же разработчик открывает транзакцию в управляемом режиме, то он должен быть уверен в том, что для записываемого в этой транзакции справочника, в свойствах метаданных указан управляемый режим блокировок в транзакции. В противном случае при записи элемента справочника будет вызвана исключительная ситуация (см. табл. 3, 2 и 4 строки).
Рассмотрим другой пример - интерактивное проведение документа, который выполняет движения по регистру накопления. В этом случае «первой» (неявной) транзакцией будет транзакция, открываемая системой при записи документа, а «второй» (также неявной) будет транзакция, открываемая системой при записи набора записей регистра накопления.
Далее все аналогично предыдущему примеру. Если для документа в метаданных установлен автоматический режим управления блокировками, то независимо от того, какой режим установлен в метаданных для регистра накопления, запись его набора записей всегда будет выполняться в автоматическом режиме.
Если же для документа установлен управляемый режим блокировок в транзакции, то для регистра накопления таже должен быть установлен управляемый режим, иначе при проведении документа будет вызвана исключительная ситуация.
Из этих примеров можно сделать следующий общий вывод. Если, например, стоит задача повысить параллельность работы при проведении отдельного документа, не переводя при этом всю конфигурацию в управляемый режим, то последовательность действий должна быть следующей:
Средствами встроенного языка установка управляемых
блокировок внутри явной или скрытой (неявной) транзакции происходит с
помощью специального объекта БлокировкаДанных,
описание доступных свойств и методов которого можно посмотреть в
синтакс-помощнике в ветви Общие объекты (рис. 19).
Рис. 19. Набор свойств и методов объекта «БлокировкаДанных» доступных в Синтакс-помощнике
Новый экземпляр данного объекта может быть создан с помощью одноименного конструктора и представляет собой коллекцию элементов блокировки данных. Изначально эта коллекция пуста и задача разработчика состоит в добавлении в эту коллекцию некоторого количества элементов блокировки.
При добавлении нового элемента блокировки для него необходимо указать пространство блокировок, которое будет блокировать данный элемент. Пространства блокировок определены в платформе 1С:Предприятия 8.1 и соответствуют структуре прикладных объектов конфигурации. Допустимы следующие имена пространств блокировок и имена полей пространств блокировок (табл. 9):
Имя пространства блокировок | Поля пространства блокировок |
Справочник.<имя> | Ссылка |
Документ.<имя> | Ссылка |
ПланОбмена.<имя> | Ссылка |
ПланСчетов.<имя> | Ссылка |
БизнеcПроцесс.<имя> | Ссылка |
Задача.<имя> | Ссылка |
ПланВидовРасчета.<имя> | Ссылка |
ПланВидовХарактеристик.<имя> | Ссылка |
РегистрСведений.<имя>.НаборЗаписей - только для регистра сведений, подчиненного регистратору | Регистратор |
РегистрСведений.<имя> | Период - если есть; <имя измерения> |
РегистрНакопления.<имя>.НаборЗаписей | Регистратор |
РегистрНакопления.<имя> | Период; <имя измерения> |
РегистрБухгалтерии.<имя>.НаборЗаписей | Регистратор |
РегистрБухгалтерии.<имя> | Период; <вид движения> - значение системного перечисления ВидДвиженияБухгалтерии; Счет - обязательное поле; Субконто; <вид субконто>; <имя измерения> |
РегистрРасчета.<имя>.НаборЗаписей | Регистратор |
РегистрРасчета.<имя> | ПериодРегистрации; ПериодДействия; <имя измерения> |
Перерасчет.<имя>.НаборЗаписей | ОбъектПерерасчета |
Перерасчет.<имя> | ВидРасчета |
Последовательность.<имя>.НаборЗаписей | Регистратор |
Последовательность.<имя> | <имя измерения> |
Константа.<имя> |
|
ВНИМАНИЕ
Следует понимать, что, в данном случае речь не идет о реальных записях базы данных. Несмотря на то, что управляемые блокировки описываются в терминах объектов метаданных и их полей, эти блокировки никак не связаны с реальной структурой хранения данных 1С:Предприятия в СУБД. Это всего лишь записи о том, что заблокировано «нечто».
Иногда можно провести аналогию между управляемыми блокировками и реальными записями СУБД. Например, для объектных данных блокировка объекта с указанной ссылкой будет «соответствовать» блокировке всех записей, содержащих указанную ссылку, во всех таблицах этого объекта метаданных (в основной таблице и в таблицах его табличных частей).
Однако в других случаях провести такую аналогию достаточно затруднительно, да и не нужно. Например, блокировка регистра бухгалтерии с указанием значения вида субконто. Достаточно понимать, что накладывая такую блокировку мы запрещаем другим транзакциям каким-либо образом изменять «записи» регистра бухгалтерии, у которых значение вида субконто равно указанному нами. Как при этом данное условие «проецируется» на реальную структуру данных регистра бухгалтерии - для нас совершенно не важно.
При установке новых блокировок менеджер анализирует имеющиеся блокировки. Если оказывается, что «нечто», что мы пытаемся заблокировать, уже заблокировано ранее, сравниваются режимы существующей и новой блокировок. Если режимы совместимы - новая блокировка устанавливается. Если режимы не совместимы - новая блокировка ожидает снятия существующей блокировки.
Условия необходимо ставить именно на те поля, имена которых приведены в списке имен пространств блокировок. Для каждого пространства блокировок количество устанавливаемых условий не ограничено. Условия могут быть заданы или на равенство значения поля какому-либо значению, или на вхождение значения поля в указанный диапазон.
Существует два способа задания условий на поля пространств блокировки:
При явном задании имени поля и его значения необходимо использовать метод УстановитьЗначение() объекта ЭлементБлокировкиДанных. В этом случае имя и значение указывают в качестве параметров метода, например так, как показано в листинге 1:
Листинг 1. Пример установки условия блокировки записей с помощью явного указания имени поля и его значения
// Создать объект блокировка данных БлокировкаДанных = Новый БлокировкаДанных;
// Добавить новый элемент блокировки, блокирующий «нечто» в данных регистра накопления Остатки номенклатуры ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
// Установить режим блокировки - исключительный. Другие транзакции, устанавливающие управляемые // блокировки, не смогут даже начать чтение этих данных ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Указать, что именно мы блокируем в данных регистра Остатки номенклатуры - все «записи», у которых // значение измерения Склад равно значению, содержащемуся в переменной Склад ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
Для значений типа Дата или Число в качестве значения может быть задан некоторый диапазон значений. Диапазон значений передается методу с помощью объекта встроенного языка - Диапазон. Данный объект позволяет задать верхнюю и нижнюю границы диапазона, причем в диапазон включаются и границы диапазона (листинг 2).
Листинг 2. Пример установки условия блокировки записей с помощью задания диапазона
// Создать объект блокировка данных БлокировкаДанных = Новый БлокировкаДанных;
// Добавить новый элемент блокировки, блокирующий «нечто» в данных регистра накопления Продажи ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрНакопления.Продажи");
// Установить режим блокировки - разделяемый. Эти данные гарантировано не будут изменены другими // транзакциями до окончания существующей транзакции ЭлементБлокировки.Режим = РежимБлокировкиДанных. Разделяемый;
// Указать, что именно мы блокируем в данных регистра Продажи - все «записи», у которых // значение измерения Контрагент равно значению, содержащемуся в переменной Контрагент ЭлементБлокировки.УстановитьЗначение("Контрагент", Контрагент);
// Создать объект Диапазон, описывающих интервал от начала месяца, к которому принадлежит указанная дата, // до указанной даты Диапазон = Новый Диапазон(НачалоМесяца(Дата), Дата);
// Указать, что именно мы блокируем в данных регистра Продажи - все «записи», у которых // значение измерения Контрагент равно значению, содержащемуся в переменной Контрагент, // и значение поля Период содержится в указанном диапазоне ЭлементБлокировки.УстановитьЗначение("Период", Диапазон);
При указании источника данных сначала необходимо задать свойство ИсточникДанных объекта ЭлементБлокировкиДанных, после чего, используя метод ИспользоватьИзИсточникаДанных(), настроить соответствие полей области блокировки данных полям источника данных (листинг 3).
Листинг 3. Пример установки условия блокировки записей с помощью источника данных
// Создать объект блокировка данных БлокировкаДанных = Новый БлокировкаДанных;
// Добавить новый элемент блокировки, блокирующий «нечто» в данных регистра накопления Остатки номенклатуры ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
// Установить режим блокировки - исключительный. Другие транзакции, устанавливающие управляемые // блокировки, не смогут даже начать чтение этих данных ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Указать, что именно мы блокируем в данных регистра Остатки номенклатуры - все «записи», у которых // значение измерения Склад равно значению, содержащемуся в переменной Склад ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
// Указать источник данных, который содержит данные для установки ограничений на другие поля этого // элемента блокировки - в данном случае таблица значений СписокНоменклатуры ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
// Указать, что именно мы блокируем в данных регистра Остатки номенклатуры - все «записи», у которых // значение измерения Склад равно значению, содержащемуся в переменной Склад, // и у которых значение измерения Номенклатура равно какому-либо значению, содержащемуся в колонке // Номенклатура указанного источника данных ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура");
В качестве источника данных можно указывать результат запроса, табличную часть, набор записей или таблицу значений. При установке соответствия полей, именами полей источника будут являться имена колонок результата запроса, имена реквизитов табличной части, имена измерений или имена колонок таблицы значений соответственно. Заметим, что объект Диапазон также может являться значением поля источника данных.
Для установки всех созданных нами блокировок используется метод объекта БлокировкаДанных - Заблокировать(). На рисунке 20 показано действие данного метода в случае использования его внутри транзакции и вне ее.
Рис. 20. Схема вызова метода «Заблокировать()» объекта «БлокировкаДанных»
Как следует из рисунка, если этот метод выполняется внутри транзакции (явной или неявной), то блокировки устанавливаются в момент вызова метода. При окончании транзакции они будут сняты автоматически. Если же метод Заблокировать() выполняется вне транзакции, то блокировки установлены не будут.
При переводе созданных ранее прикладных решений с версии 1С:Предприятие 8.0 на версию 1С:Предприятие 8.1 они требуют определенной степени доработки с точки зрения перехода к работе в режиме управляемых блокировок. Дадим ряд рекомендаций, позволяющих определить последовательность действий разработчика в случае реализации вышеупомянутого перевода:
ВНИМАНИЕ
В режиме управляемых блокировок, за счет использования другого уровня изоляции транзакций СУБД, конструкция ДЛЯ ИЗМЕНЕНИЯ языка запросов не работает. Таким образом, если в транзакции встречаются запросы, содержащие эту конструкцию, перед их выполнением необходимо устанавливать исключительную управляемую блокировку на читаемые данные. Это позволит в управляемом режиме обеспечить поведение, аналогичное поведению в автоматическом режиме.
Следует помнить, что чтение данных другими транзакциями будет невозможно только в том случае, если в других транзакциях устанавливаются несовместимые управляемые блокировки. Если управляемые блокировки в других транзакциях не устанавливаются, то чтение будет возможно. Это аналогично тому, как конструкция ДЛЯ ИЗМЕНЕНИЯ препятствует чтению данных не любыми запросами, а только теми, которые тоже используют конструкцию ДЛЯ ИЗМЕНЕНИЯ.
В качестве примера, приведем фрагмент программного кода из текста обработчика ОбработкаПроведения(), расположенного в модуле документа РасходнаяНакладная (листинг 4). При выполнении указанного программного кода система, используя механизм запросов, сначала читает информацию из регистра накопления ОстаткиНоменклатуры, а потом записывает в тот же самый регистр вновь сформированные данные. Согласно нашим рекомендациям, мы должны установить на записи регистра исключительную блокировку, запрещающую другим транзакциям, в которых устанавливаюся управляемые блокировки, не только запись, но и чтение, изменяемых при проведении накладной записей. В данном случае исключительная блокировка нужна для предотвращения возможного конфликта блокировок (deadlock).
Разбирая программный код, обратите внимание на соответствие условий при блокировке данных и в тексте запроса.
Листинг 4. Пример установки исключительной блокировки при проведении документа «РасходнаяНакладная»
БлокировкаДанных = Новый БлокировкаДанных;
// Выбрать пространство блокировок РегистрНакопления.ОстаткиНоменклатуры, т.к. мы собираемся анализировать // остатки регистра ЭлементБлокировки = БлокировкаДанных.Добавить("РегистрНакопления.ОстаткиНоменклатуры");
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
// Заблокировать «записи» с указанным значением измерения склад, т.к. в запросе есть следующее условие: // ... И Склад = &Склад) ... ЭлементБлокировки.УстановитьЗначение("Склад", Склад);
// Заблокировать «записи» со значениями номенклатуры из табличной части, соответствующими условию // ... РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Услуга = ЛОЖЬ ... // эти данные необходимо получить запросом к табличной части ЗапросИсточник = Новый Запрос; ЗапросИсточник.Текст = "ВЫБРАТЬ РАЗЛИЧНЫЕ | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура |ИЗ | Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры |ГДЕ | РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка | И РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Услуга = ЛОЖЬ"; ЗапросИсточник.УстановитьПараметр("Ссылка", Ссылка); ИсточникНоменклатуры = ЗапросИсточник.Выполнить();
ЭлементБлокировки.ИсточникДанных = ИсточникНоменклатуры; ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура", "Номенклатура"); БлокировкаДанных.Заблокировать();
Запрос = Новый Запрос; Запрос.Текст = "ВЫБРАТЬ . . . . . . . . . . | ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(&МомВремени, | Номенклатура В (ВЫБРАТЬ РАЗЛИЧНЫЕ | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура | ИЗ | Документ.РасходнаяНакладная.СписокНоменклатуры КАК | РасходнаяНакладнаяСписокНоменклатуры | ГДЕ | РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка | И РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Услуга = ЛОЖЬ) | И Склад = &Склад) КАК ОстаткиНоменклатурыОстатки
. . . . . . . . . . |"; . . . . . . . . . . Пока Выборка.Следующий() Цикл . . . . . . . . . . // регистр ОстаткиНоменклатуры Расход Движение = Движения.ОстаткиНоменклатуры.Добавить(); Движение.ВидДвижения = ВидДвиженияНакопления.Расход; Движение.Период = Дата; Движение.Номенклатура = Выборка.Номенклатура; Движение.Склад = Склад; Движение.Количество = Выборка.Количество; СуммаСписания = ?(Выборка.КоличествоОстаток = Выборка.Количество, Выборка.СуммаОстаток, Выборка.СуммаОстаток / Выборка.КоличествоОстаток * Выборка.Количество); Движение.Сумма = СуммаСписания; КонецЦикла; Движения.ОстаткиНоменклатуры.Записать();
Обратите внимание, что при создании источника данных для установки блокировок по номенклатуре мы использовали результат запроса к табличной части. Казалось бы, в качестве источника данных можно было просто использовать табличную часть (ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры), но при этом мы бы заблокировали лишние данные, ведь нас интересует только та номенклатура из табличной части, которая не является услугой (РасходнаяНакладнаяСписокНоменклатуры.Номенклатура.Услуга = ЛОЖЬ). Этот момент как раз хорошо иллюстрирует тот факт, что к установке управляемых блокировок нужно относиться внимательно и не устанавливать «плохих» (избыточных) блокировок.
Теперь расмотрим подробнее вопрос необходимости установки именно исключительной управляемой блокировки.
Если бы мы не использовали никакой управляемой блокировки, то наш запрос, читающий данные, начал бы выполняться в любом случае. После его окончания (еще до окончания транзакции!) СУБД сняла бы блокировку с прочитанных данных. Это значит, что другая транзакция тут же могла бы эти данные изменить (таковы особенности блокировок на уровне изоляции Read Committed). Поэтому управляемая блокировка необходима для того, чтобы гарантировать, что прочитанные данные не будут изменены до окончания нашей транзакции.
Каким образом управляемая блокировка препятствует изменению данных? В результате выполнения метода Заблокировать() в менеджере транзакционных блокировок появляются записи о тех данных, которые мы блокируем (естественно, если они не конфликтуют с существующими блокировками) (рис. 21).
Рис. 21. Установка управляемых блокировок
При любой попытке модификации данных информационной базы система автоматически будет пытаться установить исключительные управляемые блокировки для модифицируемых данных. Таким образом, если окажется, что другая транзакция пытается модифицировать те данные, которые мы уже заблокировали, менеджер транзакционных блокировок не даст ей установить нужные блокировки, т.к. исключительная блокировка, которую пытается установить другая транзакция, не совместима с нашей разделяемой блокировкой (рис. 22).
Рис. 22. Невозможно установить исключительную блокировку на номенклатуру 2
Значит система не сможет изменить данные, заблокированные нами до тех пор, пока наша транзакция не закончится и наша блокировка не будет снята автоматически при завершении транзакции (рис. 23).
Рис. 23. Исполнение кода продолжается после завершения нашей транзакции
Если бы мы использовали только разделяемую управляемую блокировку, то мы могли бы получить конфликт блокировок (deadlock) с другой транзакцией.
Например, наша и другая транзакция начали читать остатки из регистра Остатки номенклатуры. Списки номенклатуры, для которых читаются остатки в одной и другой транзакции, имеют одинаковые элементы. При этом система в нашей и в другой транзакции устанавливает разделяемую блокировку на читаемые данные. Две разделяемые блокировки на один и тот же ресурс («пересекающиеся» элементы номенклатуры) совместимы друг с другом (рис. 24).
Рис. 24. Разделяемые блокировки совместимы друг с другом
Затем мы закончили читать данные и хотим записать в этот регистр «новые остатки» для прочитанной номенклатуры. Мы не можем это сделать, т.к. для этого система должна установить исключительную блокировку на записываемые данные, но этому мешает разделяемая блокировка, установленная на часть этих данных другой транзакцией. Наша транзакция становится в очередь, ожидая снятия разделяемой блокировки, установленной другой транзакцией (рис. 25).
Рис. 25. Наша транзакция не может установить исключительную блокировку на номенклатуру 2
В это время другая транзакция также закончила чтение данных и хочет записать в этот регистр «новые остатки» для прочитанной номенклатуры. Она не может это сделать, т.к. для этого система должна установить исключительную блокировку на записываемые данные, но этому мешает разделяемая блокировка, установленная на часть этих данных нашей транзакцией. Другая транзакция становится в очередь, ожидая снятия разделяемой блокировки, установленной нашей транзакцией транзакцией (рис. 26).
Рис. 26. Конфликт блокировок: другая транзакция также не может установить блокировку на номенклатуру 2
В результате получается конфликт блокировок (deadlock), который может быть разрешен только принудительной отменой одной из транзакций.
Поэтому в данном случае необходима исключительная блокировка (рис. 27).
Рис. 27. Установка исключительных блокировок
В этом случае, когда другая транзакция, использующая управляемые блокировки, попытается начать чтение данных, которые мы заблокировали, она не сможет этого сделать. Менеджер тразакционных блокировок не даст ей установить никакую управляемую блокировку (ни разделяемую, ни исключительную) на наши данные, т.к. они не совместимы с нашей исключительной блокировкой (рис. 28).
Рис. 28. Другая транзакция не может установить блокировку на номенклатуру 2
В результате исполнение кода в другой транзакции будет остановлено и система будет ожидать снятия нашей исключительной блокировки. А наша транзакция сможет завершится, т.к. другая транзакция не имеет возможности заблокировать те записи, которые понадобятся нам для модификации (рис. 29).
Рис. 29. Наша транзакция успешно завершается