Почему блокировка выполняется по регистру, а не по виртуальной таблице остатков,

База знаний Одина — Одинэсника › Форумы › ODIN — Форум по 1С Предприятию › Почему блокировка выполняется по регистру, а не по виртуальной таблице остатков,

Просмотр 1 ветки ответов
  • Автор
    Записи
    • #38864
      Фото аватараOdineski
      Участник

        Почему мы в блокировке используем таблицу РегистрНакопления.ОстаткиНоменклатуры а не РегистрНакопления.ОстаткиНоменклатуры.Остатки? Мы же именно её выбирали в запросе.
        И почему мы в блокировке блокируем в качестве источника данных табличную часть список номенклатуры, хотя она вообще относится к документу, а мы ведь заблокировали регистр, а не документ.
        Объясни смысл этой блокировки построчно.

        Процедура ОбработкаПроведения(Отказ, Режим)

        Движения.ОстаткиНоменклатуры.Записывать = Истина;
        Движения.Записать();

        Движения.ОстаткиНоменклатуры.Записывать = Истина;

        Блокировка = Новый БлокировкаДанных;
        ЭлементБлокировки = Блокировка.Добавить(«РегистрНакопления.ОстаткиНоменклатуры.Остатки»);
        ЭлементБлокировки.УстановитьЗначение(«Склад», Склад);
        ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
        ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
        ЭлементБлокировки.ИспользоватьИзИсточникаДанных(«Номенклатура», «Номенклатура»);
        Блокировка.Заблокировать();

        Запрос = Новый Запрос;
        Запрос.Текст =
        «ВЫБРАТЬ
        | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
        | СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество
        |ПОМЕСТИТЬ втТЧТовары
        |ИЗ
        | Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
        |ГДЕ
        | РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
        |
        |СГРУППИРОВАТЬ ПО
        | РасходнаяНакладнаяСписокНоменклатуры.Номенклатура
        |;
        |
        |////////////////////////////////////////////////////////////////////////////////
        |ВЫБРАТЬ
        | втТЧТовары.Номенклатура КАК Номенклатура,
        | втТЧТовары.Номенклатура.Представление КАК НоменклатураПредставление,
        | втТЧТовары.Количество КАК Количество,
        | ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
        |ИЗ
        | втТЧТовары КАК втТЧТовары
        | ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
        | &МоментВремени,
        | Номенклатура В
        | (ВЫБРАТЬ
        | втТЧТовары.Номенклатура КАК Номенклатура
        | ИЗ
        | втТЧТовары КАК втТЧТовары)
        | И Склад = &Склад) КАК ОстаткиНоменклатурыОстатки
        | ПО втТЧТовары.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура»;

        Запрос.УстановитьПараметр(«МоментВремени», МоментВремени());
        Запрос.УстановитьПараметр(«Склад», Склад);
        Запрос.УстановитьПараметр(«Ссылка», Ссылка);

        РезультатЗапроса = Запрос.Выполнить();

        Выборка = РезультатЗапроса.Выбрать();

        Пока Выборка.Следующий() Цикл
        Если Выборка.Количество > Выборка.КоличествоОстаток Тогда
        Сообщение = Новый СообщениеПользователю;
        Сообщение.Текст = «У вас не хватает товара » + Выборка.НоменклатураПредставление + » в количестве » + Выборка.Количество;
        Сообщение.Сообщить();
        Отказ = Истина;
        Продолжить;
        КонецЕсли;

        Движение = Движения.ОстаткиНоменклатуры.Добавить();
        Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
        Движение.Период = Дата;
        Движение.Номенклатура = Выборка.Номенклатура;
        Движение.Склад = Склад;
        Движение.Количество = Выборка.Количество;

        КонецЦикла;

        КонецПроцедуры

      • #38865
        bot_avaOdin — Одинэсник
        Участник

          В вашем коде смешаны три разных понятия:

          1. Физический регистр накопления.
          2. Виртуальная таблица регистра .Остатки.
          3. Табличная часть документа, которая используется как источник ключей для блокировки.

          Разберем по порядку.

          ## 1. Почему в блокировке используется регистр без .Остатки

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

          А в запросе остатки получаются через виртуальную таблицу:

          Это разные конструкции.

          ### Физический регистр

          Это объект метаданных, в котором хранятся движения:

          Именно этот объект можно заблокировать.

          ### Виртуальная таблица остатков

          Это не отдельная таблица хранения. Это виртуальная таблица, которую платформа рассчитывает по движениям регистра.

          Она возвращает рассчитанный результат:

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

          Поэтому:

          Нужно:

          Проще запомнить так:

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

          ## 2. Почему источник блокировки является табличной частью документа

          Вот этот код:

          не означает, что блокируется табличная часть документа.

          Табличная часть здесь используется только как источник значений для формирования блокировки.

          То есть платформа берет из СписокНоменклатуры значения колонки Номенклатура и строит условие блокировки примерно такого вида:

          Блокируется не документ и не табличная часть.

          Блокируется именно регистр:

          Табличная часть только сообщает, по каким значениям измерения Номенклатура нужно блокировать регистр.

          ## 3. Что делает ИсточникДанных

          Рассмотрим эти строки:

          Вызов:

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

          Вызов:

          устанавливает соответствие:

          Левый параметр:

          это имя измерения регистра.

          Правый параметр:

          это имя колонки табличной части.

          В вашем случае имена совпадают, поэтому код выглядит симметрично. Они могут называться по-разному:

          Это означало бы:

          ## 4. Разбор блокировки построчно

          Ваш фрагмент:

          Создается объект управляемой блокировки данных.

          Пока вызван только конструктор, никакие данные еще не заблокированы.

          В объект блокировки добавляется один элемент.

          Он указывает, какой объект данных нужно блокировать:

          Правильный вариант должен быть именно без .Остатки:

          Устанавливается фиксированное значение измерения Склад.

          Если текущий документ оформляется по складу Склад1, то блокировка будет ограничена этим складом:

          Пока измерение Номенклатура еще не задано, условие можно представить так:

          Следующая настройка ограничит его еще и по номенклатуре.

          Устанавливается исключительный режим блокировки.

          Смысл:

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

          Если второй пользователь одновременно пытается провести документ по тем же складу и товарам, его блокировка будет конфликтовать с первой.

          Это предотвращает ситуацию:

          При правильной блокировке происходит иначе:

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

          Источником значений для отбора является табличная часть текущего документа.

          Платформа будет брать из нее строки, например:

          Эта строка не блокирует табличную часть. Она только передает значения для блокировки регистра.

          Настраивается отбор:

          После этой строки итоговая блокировка концептуально становится такой:

          Именно здесь блокировка реально устанавливается.

          До вызова:

          объект только настраивается.

          После вызова:

          текущая транзакция пытается получить указанные блокировки.

          Только после успешного получения блокировки нужно читать остатки:

          ## 5. Что именно блокируется в вашем случае

          Предположим:

          Тогда блокировка концептуально будет такой:

          Она не блокирует:

          Она блокирует нужный участок регистра по аналитикам, участвующим в списании.

          ## 6. Почему источник лучше брать из табличной части

          Потому что табличная часть содержит конкретный набор товаров, с которыми работает документ.

          Если вручную формировать список блокируемой номенклатуры, пришлось бы сначала:

          1. прочитать табличную часть;
          2. собрать массив или таблицу значений;
          3. передать ее в блокировку.

          При использовании источника данных платформа может использовать табличную часть напрямую:

          Это удобно и наглядно.

          Кроме того, блокировка получается ограниченной:

          ## 7. Но в вашем коде есть ошибка в имени регистра

          Сейчас у вас:

          Нужно исправить на:

          .Остатки используется в запросе:

          А в блокировке используется имя объекта данных:

          ## 8. Еще одна важная ошибка: движения записываются до блокировки

          В начале процедуры у вас:

          Вторая строка:

          дублируется и сама по себе не нужна.

          Первый вызов:

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

          Но в вашем коде это выполняется до блокировки:

          Надежнее сначала установить блокировку, а затем записать набор движений, чтобы исключить старые движения текущего документа:

          Зачем вообще нужен Движения.Записать() до запроса?

          Представим документ уже проводился раньше:

          Теперь пользователь изменил документ, и новый расход должен быть 5.

          Если читать остаток, не удалив старое движение, можно получить:

          Но для повторной проверки нужно учитывать состояние без старого варианта этого же документа:

          И только после проверки необходимо записать новое движение расхода 5, получив остаток 5.

          ## 9. Поле Количество в блокировке не используется

          Вы блокируете регистр по:

          Поле Количество не нужно блокировать, потому что это ресурс, а не измерение.

          Блокировка устанавливается по аналитикам, определяющим остаток.

          Если регистр имеет структуру:

          то блокировать нужно:

          Если в регистре появятся дополнительные измерения:

          то их тоже нужно учитывать в блокировке:

          Иначе можно заблокировать не тот уровень аналитики, который реально используется при расчете остатков.

          ## 10. В вашем запросе блокировка и отбор должны соответствовать друг другу

          В запросе вы получаете остатки по:

          В блокировке должны использоваться те же аналитики:

          Это важный принцип:

          Если запрос читает остатки по складу и номенклатуре, а блокировка установлена только по складу, то блокировка будет слишком широкой.

          Если запрос читает по складу, номенклатуре и серии, а блокировка установлена только по складу и номенклатуре, то блокировка будет недостаточно точной.

          ## 11. Исправленный фрагмент проведения

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

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

          потом проверить таблицу, а затем пройти по ней второй раз для формирования движений.

          ## 12. Что в исходном коде нужно исправить

          В вашем варианте я бы исправил следующее:

          ### Ошибка 1. Имя объекта блокировки

          Было:

          Нужно:

          ### Ошибка 2. Дублирование установки записи движений

          Было:

          Вторая строка лишняя.

          ### Ошибка 3. Старые движения удаляются до блокировки

          Лучше устанавливать блокировку до:

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

          ### Ошибка 4. Сообщение показывает не нехватку, а требуемое количество

          Сейчас:

          Здесь выводится количество, которое требуется списать, а не количество нехватки.

          Правильнее:

          И выводить Нехватка.

          ### Ошибка 5. Движения добавляются даже после обнаружения нехватки других товаров

          Вы устанавливаете:

          но продолжаете обход и добавляете движения по товарам, которых хватает.

          Обычно лучше сначала проверить весь документ, а движения формировать только если:

          ### Ошибка 6. При повторном проведении нужно учитывать старые движения

          Именно для этого в начале проведения часто выполняют:

          Но делать это нужно в правильной последовательности относительно блокировки и запроса.

      Просмотр 1 ветки ответов
      • Для ответа в этой теме необходимо авторизоваться.
      База знаний 1С