Что бы понять материал данной статьи, нужно хорошо знать и понимать 2 вещи:
1. Что такое режим разделения итогов. Если вы вдруг не в курсе, то рекомендую обратиться к данной статье на ИТС. Там все очень подробно и понятно написано.
2. Новая методика контроля остатков (остатки контролируются после записи)
Режим разделения итогов очень полезен в том случае, если не нужно делать контроль остатков по регистру т.к. можно параллельно записывать данные с одинаковым набором измерений.
Если по регистру необходимо всегда контролировать остатки, то лучше не использовать режим разделения итогов т.к. выигрыша в параллельности он не дает.
Но как быть, если например у регистра есть 2 регистратора, и один документ использует контроль остатков, а второй нет?
В этом случае нам как раз и пригодится свойство набора записей регистров накопления и бухгалтерии «БлокироватьДляИзменения».
Начнем с того, что использовать «БлокироватьДляИзменения» имеет смысл, только если выполняются все следующие условия:
- транзакция выполняется в управляемом режиме (если использовать в автоматическом, то возникнет ошибка)
- у регистра включен режим разделения итогов (если он выключен, то свойство не имеет смысла и будет просто игнорироваться)
- используется новая методика контроля остатков (остатки контролируются после записи) (в старой методике не имеет смысла, т.к. там нужно накладывать блокировку до записи в регистр)
Если хотя бы одно из этих условий не выполняется, то нет смысла использовать «БлокироватьДляИзменения».
Поэтому все сказанное ниже применимо только если выполняются все 3 условия.
А что это свойство делает?
Это свойство делает одну единственную вещь, оно игнорирует разделитель итогов по записываемому набору измерений, начиная с момента записи до конца транзакции.
А поподробнее?
Тут необходимо расписать каждую фразу, что бы исключить неправильную трактовку.
«… оно игнорирует разделитель итогов»
Несмотря на название, это свойство на самом деле ничего не блокирует, набор записей автоматически блокируются платформой при записи т.к. используется управляемый режим.
Повторю еще раз, свойство фактически ничего не блокирует, блокирует платформа без вашего участия, свойство лишь на время игнорирует разделитель итогов по данному набору измерений.
А зачем вообще игнорировать разделитель итогов?
Если не отключить разделитель итогов, то платформа накладывает управляемую блокировку по ключу Склад+Товар+Разделитель.
В результате возможны следующие последствия:
При использовании СУБД блокировочника - возможна взаимоблокировка.
При использовании версионника - возможно списание остатков в минус.
Давайте разберем эти случаи подробнее.
Если используется блокировочник.
В случае с блокировочником, дедлок получается довольно просто, две транзакции параллельно записывают свои данные по одному набору измерений, платформа это позволяет т.к. используется разделитель итогов. После этого обе транзакции хотят проверить остатки, а для этого нужны все строки по ключу Склад+Товар (т.е. без учета разделителя). В итоге ни одна из транзакций не может прочитать все строки, т.к. часть строк захвачена другой транзакцией (разделитль "мешает" захватить все строки).
Схема этой взаимоблокировки представлена в таблице.
В этом случае, по своему назначению, «БлокироватьДляИзменения» это аналог параметра «ДЛЯ ИЗМЕНЕНИЯ» в запросе чтения остатков в автоматическом режиме, только работает иначе.
Если включить «БлокироватьДляИзменения», то мы отключим разделитель итогов, и при записи сразу будет наложена управляемая блокировка Склад+Товар, и транзакция 2 не сможет добавить вторую строку, т.к. данный набор измерений будет уже заблокирован.
При использовании «БлокироватьДляИзменения», блокировка является следствием отключения разделителя итогов для предотвращения дедлока. Можно сказать, что это своего рода полезный побочный эффект.
Но даже если бы мы не использовали БлокироватьДляИзменения (и рискнули нарваться на дедлок), то остатки в минус все равно бы не списались, т.к. все нужные строки по ключу Склад+Товар (без учета разделителя) были бы заблокированы запросом остатков.
Много путаницы возникает из-за названия этого свойства, поэтому сразу непонятно как оно работает. В данном случае название не соответствует тому, что свойство делает на самом деле (оно ничего не блокирует, оно игнорирует разделитель), но название соответствует получаемому результату (в итоге получаем блокировку по всем нужным записям).
Если используется версионник
В этом случае, заблокированные строки первой транзакции, никак не помешают 2-й транзакции прочитать остатки (т.к. в версионнике пишущие не блокируют читающих).
Вторая транзакция спокойно прочитает старую версию данных (версию на момент начала первой транзакции) и спишет остаток в минус.
Что бы этого не допустить нужна управляемая блокировка на уровне 1С.
Если поставить БлокироватьДляИзменения = Истина, мы как раз получим такую блокировку, и данные будут заблокированы на уровне платформы.
«по записываемому набору измерений»
Разделитель игнорируется только для данного набора измерений.
Это значит, что режим разделения итогов на регистр в целом сохраняется и если у других наборов измерений БлокироватьДляИзменения = Ложь, то они могут быть записаны параллельно.
Пример:
Допустим что одновременно встретились 6 транзакций и попытались одновременно записать данные в регистр, при этом разделитель итогов естественно включен.
Первые 2 транзакции делают расход и контролируют остатки, другие 4 делают приход и контроль остатков им не нужен.
Тогда мы получим такую картину.
Транзакция 1 оказалась на долю секунды быстрее всех и в момент записи данных установила исключительную управляемую блокировку без учета разделителя итогов (т.к. БлокироватьДляИзменения = Истина)
Транзакции 2 и 6 ждут Транзакцию 1, т.к. набор измерений у них одинаковый.
В данном случае без разницы какое значение «БлокироватьДляИзменения» установлено для транзакций 2 и 6, важно что набор измерений одинаковый, и он уже заблокирован, так что они не смогут параллельно записать свои данные.
Транзакции 3 и 4 успешно записали свои данные параллельно, т.к. включен разделитель (БлокироватьДляИзменения = Ложь).
Транзакция 5 успешно записала данные параллельно, т.к. используется отличный от транзакции 1 набор измерений.
«начиная с момента записи до конца транзакции»
Разделитель итогов игнорируется только в тот момент, когда начинается запись данных регистра и действует до тех пор, пока не закончится транзакция проведения документа.
Это значит, что включить данное свойство можно в любом месте кода, но действовать оно начнет только в момент записи данных в регистр.
При этом свойство действует, пока не завершилась вся транзакция, т.е. если запись в регистр завершилась, а транзакция продолжается, то свойство так же продолжает работать.
Таким образом, если произойдет откат транзакции, свойство автоматически примет значение «Ложь»
А что будет, если установить БлокироватьДляИзменения = Истина для регистра, у которого выключен режим разделения итогов?
Ровным счетом ничего, не будет даже ошибки, просто данная строка кода будет проигнорирована.
А как же явные управляемые блокировки, они теперь не нужны?
Да, это еще один плюс данного свойства. При его использовании не нужно писать явные управляемые блокировки, достаточно одной строки Движения.НужныйРегистр.БлокироватьДляИзменения = Истина, это делает код компактнее и читабельнее.
А можно пример?
Допустим, есть документ реализация, который делает движение по одному регистру с контролем остатков, при этом по данному регистру включен разделитель итогов.
В таком случае код модуля проведения будет выглядеть следующим образом:
Процедура ОбработкаПроведения(Отказ, Режим)
// регистр ОстаткиТоваров Расход
Движения.ОстаткиТоваров.Записывать = Истина;
Для Каждого ТекСтрокаТабличнаяЧасть Из ТабличнаяЧасть Цикл
Движение = Движения.ОстаткиТоваров.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Склад = Склад;
Движение.Товар = ТекСтрокаТабличнаяЧасть.Товар;
Движение.Количество = ТекСтрокаТабличнаяЧасть.Количество;
КонецЦикла;
Движения.ОстаткиТоваров.БлокироватьДляИзменения = Истина;
Движения.Записать();
// Далее код проверки остатков …
КонецПроцедуры
Строку «Движения.ОстаткиТоваров.БлокироватьДляИзменения = Истина;» можно писать в любом месте процедуры, для удобства лучше это делать в конце.
Код с проверкой остатков, так же можно написать в модуле набора записей регистра, но в этом случае нужно делать проверку на тип документа, т.к. для некоторых документов контроль не нужен.
Если по данному регистру всегда нужен контроль остатков, то тогда, как уже было сказано выше, лучше вообще отключить режим разделения итогов для данного регистра т.к. выигрыша в параллельности работы не будет.
Выводы:
- Данное свойство нужно использовать, только если соблюдаются все 3 условия:
- транзакция выполняется в управляемом режиме
- у регистра включен режим разделения итогов
- используется новая методика контроля остатов (остатки контролируются после записи)
- Несмотря на название, свойство ничего не блокирует, оно просто говорит платформе что блокировка нужна без учета разделителя.
- При использовании «БлокироватьДляИзменения», явные управляемые блокировки ставить не нужно.