База знаний Одина — Одинэсника › Форумы › ODIN — Форум по 1С Предприятию › Почему блокировка выполняется по регистру, а не по виртуальной таблице остатков,
- В этой теме 1 ответ, 2 участника, последнее обновление 7 минут назад сделано
Odin — Одинэсник.
-
АвторЗаписи
-
-
18 октября 2026 в 13:00 #38864
Почему мы в блокировке используем таблицу РегистрНакопления.ОстаткиНоменклатуры а не РегистрНакопления.ОстаткиНоменклатуры.Остатки? Мы же именно её выбирали в запросе.
И почему мы в блокировке блокируем в качестве источника данных табличную часть список номенклатуры, хотя она вообще относится к документу, а мы ведь заблокировали регистр, а не документ.
Объясни смысл этой блокировки построчно.Процедура ОбработкаПроведения(Отказ, Режим)
Движения.ОстаткиНоменклатуры.Записывать = Истина;
Движения.Записать();Движения.ОстаткиНоменклатуры.Записывать = Истина;
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить(«РегистрНакопления.ОстаткиНоменклатуры.Остатки»);
ЭлементБлокировки.УстановитьЗначение(«Склад», Склад);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных(«Номенклатура», «Номенклатура»);
Блокировка.Заблокировать();Запрос = Новый Запрос;
Запрос.Текст =
«ВЫБРАТЬ
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,
| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество
|ПОМЕСТИТЬ втТЧТовары
|ИЗ
| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры
|ГДЕ
| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка
|
|СГРУППИРОВАТЬ ПО
| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура
|;
|
|////////////////////////////////////////////////////////////////////////////////
|ВЫБРАТЬ
| втТЧТовары.Номенклатура КАК Номенклатура,
| втТЧТовары.Номенклатура.Представление КАК НоменклатураПредставление,
| втТЧТовары.Количество КАК Количество,
| ЕСТЬNULL(ОстаткиНоменклатурыОстатки.КоличествоОстаток, 0) КАК КоличествоОстаток
|ИЗ
| втТЧТовары КАК втТЧТовары
| ЛЕВОЕ СОЕДИНЕНИЕ РегистрНакопления.ОстаткиНоменклатуры.Остатки(
| &МоментВремени,
| Номенклатура В
| (ВЫБРАТЬ
| втТЧТовары.Номенклатура КАК Номенклатура
| ИЗ
| втТЧТовары КАК втТЧТовары)
| И Склад = &Склад) КАК ОстаткиНоменклатурыОстатки
| ПО втТЧТовары.Номенклатура = ОстаткиНоменклатурыОстатки.Номенклатура»;Запрос.УстановитьПараметр(«МоментВремени», МоментВремени());
Запрос.УстановитьПараметр(«Склад», Склад);
Запрос.УстановитьПараметр(«Ссылка», Ссылка);РезультатЗапроса = Запрос.Выполнить();
Выборка = РезультатЗапроса.Выбрать();
Пока Выборка.Следующий() Цикл
Если Выборка.Количество > Выборка.КоличествоОстаток Тогда
Сообщение = Новый СообщениеПользователю;
Сообщение.Текст = «У вас не хватает товара » + Выборка.НоменклатураПредставление + » в количестве » + Выборка.Количество;
Сообщение.Сообщить();
Отказ = Истина;
Продолжить;
КонецЕсли;Движение = Движения.ОстаткиНоменклатуры.Добавить();
Движение.ВидДвижения = ВидДвиженияНакопления.Расход;
Движение.Период = Дата;
Движение.Номенклатура = Выборка.Номенклатура;
Движение.Склад = Склад;
Движение.Количество = Выборка.Количество;КонецЦикла;
КонецПроцедуры
-
18 октября 2026 в 13:00 #38865
В вашем коде смешаны три разных понятия:
1. Физический регистр накопления.
2. Виртуальная таблица регистра.Остатки.
3. Табличная часть документа, которая используется как источник ключей для блокировки.Разберем по порядку.
## 1. Почему в блокировке используется регистр без
.ОстаткиДля блокировки нужно указывать имя самого регистра накопления:
1С (Код)123ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");А в запросе остатки получаются через виртуальную таблицу:
1С (Код)1|РегистрНакопления.ОстаткиНоменклатуры.Остатки(...)Это разные конструкции.
### Физический регистр
1С (Код)1РегистрНакопления.ОстаткиНоменклатурыЭто объект метаданных, в котором хранятся движения:
1С (Код)123456789Приход:СкладНоменклатураКоличествоРасход:СкладНоменклатураКоличествоИменно этот объект можно заблокировать.
### Виртуальная таблица остатков
1С (Код)1РегистрНакопления.ОстаткиНоменклатуры.ОстаткиЭто не отдельная таблица хранения. Это виртуальная таблица, которую платформа рассчитывает по движениям регистра.
Она возвращает рассчитанный результат:
1С (Код)123СкладНоменклатураКоличествоОстатокВиртуальная таблица существует только во время выполнения запроса. В ней нельзя устанавливать блокировку как в самостоятельном объекте данных.
Поэтому:
1С (Код)1234// Неправильная идея для блокировкиБлокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры.Остатки");Нужно:
1С (Код)1234// ПравильноБлокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");Проще запомнить так:
1С (Код)12Блокируем движения регистра.Читаем остатки через виртуальную таблицу регистра.Блокировка движений гарантирует, что пока текущая транзакция проверяет остатки и формирует свои движения, другая транзакция не сможет одновременно изменить те же самые остатки с использованием конфликтующей управляемой блокировки.
## 2. Почему источник блокировки является табличной частью документа
Вот этот код:
1С (Код)12345ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");не означает, что блокируется табличная часть документа.
Табличная часть здесь используется только как источник значений для формирования блокировки.
То есть платформа берет из
СписокНоменклатурызначения колонкиНоменклатураи строит условие блокировки примерно такого вида:1С (Код)12345678Регистр.ОстаткиНоменклатурыГДЕСклад = текущий складИ Номенклатура В (Товар 1,Товар 2,Товар 3)Блокируется не документ и не табличная часть.
Блокируется именно регистр:
1С (Код)1РегистрНакопления.ОстаткиНоменклатурыТабличная часть только сообщает, по каким значениям измерения
Номенклатуранужно блокировать регистр.## 3. Что делает
ИсточникДанныхРассмотрим эти строки:
1С (Код)123456ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");Вызов:
1С (Код)1ИсточникДанных = СписокНоменклатурыпередает платформе таблицу или табличную часть, из которой нужно взять значения.
Вызов:
1С (Код)1234ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура")устанавливает соответствие:
1С (Код)12Измерение регистра Колонка источникаНоменклатура НоменклатураЛевый параметр:
1С (Код)1"Номенклатура"это имя измерения регистра.
Правый параметр:
1С (Код)1"Номенклатура"это имя колонки табличной части.
В вашем случае имена совпадают, поэтому код выглядит симметрично. Они могут называться по-разному:
1С (Код)1234ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Товар");Это означало бы:
1С (Код)12измерение регистра Номенклатуразаполнять значениями колонки Товар## 4. Разбор блокировки построчно
Ваш фрагмент:
1С (Код)1Блокировка = Новый БлокировкаДанных;Создается объект управляемой блокировки данных.
Пока вызван только конструктор, никакие данные еще не заблокированы.
1С (Код)123ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");В объект блокировки добавляется один элемент.
Он указывает, какой объект данных нужно блокировать:
1С (Код)1РегистрНакопления.ОстаткиНоменклатурыПравильный вариант должен быть именно без
.Остатки:1С (Код)123ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");1С (Код)1234ЭлементБлокировки.УстановитьЗначение("Склад",Склад);Устанавливается фиксированное значение измерения
Склад.Если текущий документ оформляется по складу
Склад1, то блокировка будет ограничена этим складом:1С (Код)1Склад = Склад1Пока измерение
Номенклатураеще не задано, условие можно представить так:1С (Код)12Все записи регистрапо складу Склад1Следующая настройка ограничит его еще и по номенклатуре.
1С (Код)12ЭлементБлокировки.Режим =РежимБлокировкиДанных.Исключительный;Устанавливается исключительный режим блокировки.
Смысл:
> текущая транзакция получает исключительное право на изменение заблокированных наборов данных относительно других управляемых блокировок.
Если второй пользователь одновременно пытается провести документ по тем же складу и товарам, его блокировка будет конфликтовать с первой.
Это предотвращает ситуацию:
1С (Код)123456789Остаток товара: 10Документ пользователя 1: списать 7Документ пользователя 2: списать 7Оба пользователя одновременно читают остаток 10.Оба считают, что товара хватает.Оба проводят документы.Итоговый остаток становится -4.При правильной блокировке происходит иначе:
1С (Код)12345678Пользователь 1 захватывает блокировку.Пользователь 1 видит остаток 10.Пользователь 1 списывает 7.Пользователь 1 завершает транзакцию.Пользователь 2 получает блокировку после этого.Пользователь 2 видит остаток 3.Пользователь 2 получает отказ.Важно: управляемая блокировка работает только между операциями, которые также используют управляемые блокировки. Если часть кода или внешняя система работает в обход установленной схемы блокировок, защита может быть неполной.
1С (Код)12ЭлементБлокировки.ИсточникДанных =СписокНоменклатуры;Источником значений для отбора является табличная часть текущего документа.
Платформа будет брать из нее строки, например:
1С (Код)12345Номенклатура-----------Товар АТовар БТовар ВЭта строка не блокирует табличную часть. Она только передает значения для блокировки регистра.
1С (Код)1234ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");Настраивается отбор:
1С (Код)12Измерение регистра Номенклатураполучить из колонки источника НоменклатураПосле этой строки итоговая блокировка концептуально становится такой:
1С (Код)123456РегистрНакопления.ОстаткиНоменклатурыГДЕСклад = СкладИ Номенклатура В (значения из СписокНоменклатуры.Номенклатура)1С (Код)1Блокировка.Заблокировать();Именно здесь блокировка реально устанавливается.
До вызова:
1С (Код)1Блокировка.Заблокировать();объект только настраивается.
После вызова:
1С (Код)1Блокировка.Заблокировать();текущая транзакция пытается получить указанные блокировки.
Только после успешного получения блокировки нужно читать остатки:
1С (Код)1234получили блокировкупрочитали остаткисравнили с количеством документазаписали движения## 5. Что именно блокируется в вашем случае
Предположим:
1С (Код)12345Склад = Основной складСписокНоменклатуры:МолотокДрельТогда блокировка концептуально будет такой:
1С (Код)123РегистрНакопления.ОстаткиНоменклатурыСклад = Основной складНоменклатура = Молоток или ДрельОна не блокирует:
1С (Код)1234567весь документ;всю табличную часть;все склады;всю номенклатуру;виртуальную таблицу Остатки;остатки других товаров на этом складе;остатки этих товаров на других складах.Она блокирует нужный участок регистра по аналитикам, участвующим в списании.
## 6. Почему источник лучше брать из табличной части
Потому что табличная часть содержит конкретный набор товаров, с которыми работает документ.
Если вручную формировать список блокируемой номенклатуры, пришлось бы сначала:
1. прочитать табличную часть;
2. собрать массив или таблицу значений;
3. передать ее в блокировку.При использовании источника данных платформа может использовать табличную часть напрямую:
1С (Код)1ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;Это удобно и наглядно.
Кроме того, блокировка получается ограниченной:
1С (Код)123не весь регистр,а только товары текущего документана текущем складе## 7. Но в вашем коде есть ошибка в имени регистра
Сейчас у вас:
1С (Код)123ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры.Остатки");Нужно исправить на:
1С (Код)123ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");.Остаткииспользуется в запросе:1С (Код)12345|РегистрНакопления.ОстаткиНоменклатуры.Остатки(| &МоментВремени,| Номенклатура В (...)| И Склад = &Склад|)А в блокировке используется имя объекта данных:
1С (Код)1"РегистрНакопления.ОстаткиНоменклатуры"## 8. Еще одна важная ошибка: движения записываются до блокировки
В начале процедуры у вас:
1С (Код)1234Движения.ОстаткиНоменклатуры.Записывать = Истина;Движения.Записать();Движения.ОстаткиНоменклатуры.Записывать = Истина;Вторая строка:
1С (Код)1Движения.ОстаткиНоменклатуры.Записывать = Истина;дублируется и сама по себе не нужна.
Первый вызов:
1С (Код)1Движения.Записать();обычно используется для удаления старых движений текущего документа перед чтением остатков. Это нужно при перепроведении документа, чтобы его старые движения не увеличивали остаток во время проверки.
Но в вашем коде это выполняется до блокировки:
1С (Код)123записать пустые или текущие движенияпопытаться установить блокировкупрочитать остаткиНадежнее сначала установить блокировку, а затем записать набор движений, чтобы исключить старые движения текущего документа:
1С (Код)123456789101112131415161718Движения.ОстаткиНоменклатуры.Записывать = Истина;Блокировка = Новый БлокировкаДанных;ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");ЭлементБлокировки.УстановитьЗначение("Склад", Склад);ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");Блокировка.Заблокировать();// Удаляем старые движения текущего документа// из расчета остатков при перепроведении.Движения.Записать();Зачем вообще нужен
Движения.Записать()до запроса?Представим документ уже проводился раньше:
1С (Код)123Остаток до старого документа: 10Старое движение этого документа: расход 3Остаток в регистре: 7Теперь пользователь изменил документ, и новый расход должен быть 5.
Если читать остаток, не удалив старое движение, можно получить:
1С (Код)1остаток 7Но для повторной проверки нужно учитывать состояние без старого варианта этого же документа:
1С (Код)1остаток 10И только после проверки необходимо записать новое движение расхода 5, получив остаток 5.
## 9. Поле
Количествов блокировке не используетсяВы блокируете регистр по:
1С (Код)12СкладНоменклатураПоле
Количествоне нужно блокировать, потому что это ресурс, а не измерение.Блокировка устанавливается по аналитикам, определяющим остаток.
Если регистр имеет структуру:
1С (Код)123456Измерения:СкладНоменклатураРесурсы:Количеството блокировать нужно:
1С (Код)12СкладНоменклатураЕсли в регистре появятся дополнительные измерения:
1С (Код)123ХарактеристикаСерияКачеството их тоже нужно учитывать в блокировке:
1С (Код)123456789101112131415161718ЭлементБлокировки.УстановитьЗначение("Склад", Склад);ЭлементБлокировки.ИсточникДанных = СписокНоменклатуры;ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Характеристика","Характеристика");ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Серия","Серия");Иначе можно заблокировать не тот уровень аналитики, который реально используется при расчете остатков.
## 10. В вашем запросе блокировка и отбор должны соответствовать друг другу
В запросе вы получаете остатки по:
1С (Код)12|Номенклатура В (...)|И Склад = &СкладВ блокировке должны использоваться те же аналитики:
1С (Код)12Склад = Склад документаНоменклатура = номенклатура из табличной частиЭто важный принцип:
1С (Код)12Что участвует в чтении остатков,то же должно участвовать в блокировке.Если запрос читает остатки по складу и номенклатуре, а блокировка установлена только по складу, то блокировка будет слишком широкой.
Если запрос читает по складу, номенклатуре и серии, а блокировка установлена только по складу и номенклатуре, то блокировка будет недостаточно точной.
## 11. Исправленный фрагмент проведения
Для вашей структуры регистра базовый вариант может выглядеть так:
1С (Код)123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143Процедура ОбработкаПроведения(Отказ, Режим)Движения.ОстаткиНоменклатуры.Записывать = Истина;Блокировка = Новый БлокировкаДанных;ЭлементБлокировки = Блокировка.Добавить("РегистрНакопления.ОстаткиНоменклатуры");ЭлементБлокировки.УстановитьЗначение("Склад",Склад);ЭлементБлокировки.Режим =РежимБлокировкиДанных.Исключительный;ЭлементБлокировки.ИсточникДанных =СписокНоменклатуры;ЭлементБлокировки.ИспользоватьИзИсточникаДанных("Номенклатура","Номенклатура");Блокировка.Заблокировать();// При перепроведении удаляем прежние движения// текущего документа перед чтением остатков.Движения.Записать();Запрос = Новый Запрос;Запрос.Текст ="ВЫБРАТЬ| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура КАК Номенклатура,| СУММА(РасходнаяНакладнаяСписокНоменклатуры.Количество) КАК Количество|ПОМЕСТИТЬ втТЧТовары|ИЗ| Документ.РасходнаяНакладная.СписокНоменклатуры КАК РасходнаяНакладнаяСписокНоменклатуры|ГДЕ| РасходнаяНакладнаяСписокНоменклатуры.Ссылка = &Ссылка||СГРУППИРОВАТЬ ПО| РасходнаяНакладнаяСписокНоменклатуры.Номенклатура|;||////////////////////////////////////////////////////////////////////////////////|ВЫБРАТЬ| втТЧТовары.Номенклатура КАК Номенклатура,| втТЧТовары.Номенклатура.Представление КАК НоменклатураПредставление,| втТЧТовары.Количество КАК Количество,| ЕСТЬNULL(| ОстаткиНоменклатурыОстатки.КоличествоОстаток,| 0| ) КАК КоличествоОстаток|ИЗ| втТЧТовары КАК втТЧТовары| ЛЕВОЕ СОЕДИНЕНИЕ| РегистрНакопления.ОстаткиНоменклатуры.Остатки(| &МоментВремени,| Номенклатура В| (| ВЫБРАТЬ| втТЧТовары.Номенклатура| ИЗ| втТЧТовары КАК втТЧТовары| )| И Склад = &Склад| ) КАК ОстаткиНоменклатурыОстатки| ПО втТЧТовары.Номенклатура =| ОстаткиНоменклатурыОстатки.Номенклатура";Запрос.УстановитьПараметр("МоментВремени",МоментВремени());Запрос.УстановитьПараметр("Склад",Склад);Запрос.УстановитьПараметр("Ссылка",Ссылка);РезультатЗапроса = Запрос.Выполнить();Выборка = РезультатЗапроса.Выбрать();Пока Выборка.Следующий() ЦиклЕсли Выборка.Количество >Выборка.КоличествоОстаток ТогдаСообщение = Новый СообщениеПользователю;Сообщение.Текст ="Недостаточно товара "+ Выборка.НоменклатураПредставление+ ". Требуется: "+ Строка(Выборка.Количество)+ ", остаток: "+ Строка(Выборка.КоличествоОстаток)+ ", нехватка: "+ Строка(Выборка.Количество- Выборка.КоличествоОстаток);Сообщение.Сообщить();Отказ = Истина;КонецЕсли;КонецЦикла;Если Отказ ТогдаВозврат;КонецЕсли;// Движения записываются только после успешной проверки.Выборка = РезультатЗапроса.Выбрать();Пока Выборка.Следующий() ЦиклДвижение =Движения.ОстаткиНоменклатуры.Добавить();Движение.ВидДвижения =ВидДвиженияНакопления.Расход;Движение.Период = Дата;Движение.Номенклатура =Выборка.Номенклатура;Движение.Склад = Склад;Движение.Количество =Выборка.Количество;КонецЦикла;КонецПроцедурыОднако в этом варианте есть технический нюанс: после завершения первой выборки результат запроса обычно нужно получить или выбрать заново, как показано выше, либо заранее выгрузить результат в таблицу значений. На практике удобнее сначала выгрузить результат:
1С (Код)1ТаблицаОстатков = РезультатЗапроса.Выгрузить();потом проверить таблицу, а затем пройти по ней второй раз для формирования движений.
## 12. Что в исходном коде нужно исправить
В вашем варианте я бы исправил следующее:
### Ошибка 1. Имя объекта блокировки
Было:
1С (Код)1"РегистрНакопления.ОстаткиНоменклатуры.Остатки"Нужно:
1С (Код)1"РегистрНакопления.ОстаткиНоменклатуры"### Ошибка 2. Дублирование установки записи движений
Было:
1С (Код)1234Движения.ОстаткиНоменклатуры.Записывать = Истина;Движения.Записать();Движения.ОстаткиНоменклатуры.Записывать = Истина;Вторая строка лишняя.
### Ошибка 3. Старые движения удаляются до блокировки
Лучше устанавливать блокировку до:
1С (Код)1Движения.Записать();чтобы операция удаления старых движений и последующее чтение остатков проходили в защищенной последовательности.
### Ошибка 4. Сообщение показывает не нехватку, а требуемое количество
Сейчас:
1С (Код)12345Сообщение.Текст ="У вас не хватает товара "+ Выборка.НоменклатураПредставление+ " в количестве "+ Выборка.Количество;Здесь выводится количество, которое требуется списать, а не количество нехватки.
Правильнее:
1С (Код)123Нехватка =Выборка.Количество- Выборка.КоличествоОстаток;И выводить
Нехватка.### Ошибка 5. Движения добавляются даже после обнаружения нехватки других товаров
Вы устанавливаете:
1С (Код)1Отказ = Истина;но продолжаете обход и добавляете движения по товарам, которых хватает.
Обычно лучше сначала проверить весь документ, а движения формировать только если:
1С (Код)123Если Отказ ТогдаВозврат;КонецЕсли;### Ошибка 6. При повторном проведении нужно учитывать старые движения
Именно для этого в начале проведения часто выполняют:
1С (Код)12Движения.ОстаткиНоменклатуры.Записывать = Истина;Движения.Записать();Но делать это нужно в правильной последовательности относительно блокировки и запроса.
-
-
АвторЗаписи
- Для ответа в этой теме необходимо авторизоваться.