Управляемая блокировка по данным результата запроса.
&НаСервере
Процедура ВыполнитьНаСервере()
// <<КОНСТРУКТОР_ЗАПРОСА_С_ОБРАБОТКОЙ_РЕЗУЛЬТАТА
// Данный фрагмент построен конструктором.
// При повторном использовании конструктора, внесенные вручную изменения будут утеряны.
НачатьТранзакцию();
Запрос = Новый Запрос;
Запрос.Текст =
«ВЫБРАТЬ
| ВзаиморасчетыОстатки.Договор,
| ВзаиморасчетыОстатки.Номенклатура,
| СУММА(ВзаиморасчетыОстатки.КоличествоОстаток) КАК КоличествоОстаток,
| СУММА(ВзаиморасчетыОстатки.СуммаОстаток) КАК СуммаОстаток,
| СУММА(ВзаиморасчетыОстатки.СуммаОстаток * ВзаиморасчетыОстатки.Договор.Процент / 100) КАК Процент,
| ВзаиморасчетыОстатки.Договор.Владелец КАК ДоговорВладелец
|ИЗ
| РегистрНакопления.Взаиморасчеты.Остатки(&Период, ) КАК ВзаиморасчетыОстатки
|
|СГРУППИРОВАТЬ ПО
| ВзаиморасчетыОстатки.Договор,
| ВзаиморасчетыОстатки.Номенклатура
|ИТОГИ ПО
| ДоговорВладелец»;
Запрос.УстановитьПараметр(«Период»,КонецДня(Дата));
Результат = Запрос.Выполнить();
Блокировка = Новый БлокировкаДанных;
ЭлементБлокировки = Блокировка.Добавить(«РегистрНакопления.Взаиморасчеты»);
ЭлементБлокировки.Режим = РежимБлокировкиДанных.Исключительный;
ЭлементБлокировки.ИсточникДанных = Результат;
ЭлементБлокировки.ИспользоватьИзИсточникаДанных(«Номенклатура», «Номенклатура»);
ЭлементБлокировки.ИспользоватьИзИсточникаДанных(«Договор», «Договор»);
Блокировка.Заблокировать();
ВыборкаКонтрагентов = Результат.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам);
Пока ВыборкаКонтрагентов.Следующий() Цикл
Документ = Документы.РасходДенег.СоздатьДокумент();
Документ.Дата = Дата;
Документ.Контрагент = ВыборкаКонтрагентов.ДоговорВладелец;
Выборка = ВыборкаКонтрагентов.Выбрать(ОбходРезультатаЗапроса.ПоГруппировкам,»»);
Пока Выборка.Следующий() Цикл
Строчка = Документ.СписокДоговоров.Добавить();
Строчка.Договор = Выборка.Договор;
Строчка.Номенклатура = Выборка.Номенклатура;
Строчка.Количество = Выборка.КоличествоОстаток;
Строчка.Сумма = Выборка.СуммаОстаток;
Строчка.Вознаграждение = ВЫборка.Процент;
КонецЦикла;
//Попытка
Документ.Записать(РежимЗаписиДокумента.Проведение);
//Исключение
// Документ.Записать(РежимЗаписиДокумента.Проведение);
//КонецПопытки;
КонецЦикла;
Такой код выдает ошибку при установке блокировки, т.к. есть итоговые записи по контрагентам, в которых договор и номенклатура NULL.
Т.к. задача на спеца по платформе, хочется выбрать самый кошерный способ. Есть ли что-нибудь лучше, чем лишний обход детальных записей с созданием доп. таблицы значений?
1С 8.x : Использование предложения ДЛЯ ИЗМЕНЕНИЯ
Предложение ДЛЯ ИЗМЕНЕНИЯ позволяет заблаговременно заблокировать некоторые данные (которые могут читаться транзакцией другого соединения) уже при считывании, чтобы исключить взаимные блокировки при записи. ДЛЯ ИЗМЕНЕНИЯ дает возможность указать в запросе таблицы, считываемые данные которых предполагается изменять. В этом случае другое соединение будет ожидать освобождения этих данных уже в момент считывания внутри транзакции, т.е. не сможет прочесть заблокированные данные до тех пор, пока не будет завершена транзакция, наложившая блокировку. Блокировка от изменения данных считываемых в транзакции выполняется независимо от предложения ДЛЯ ИЗМЕНЕНИЯ. Это значит, что если внутри какой-либо транзакции считаны некоторые данные, то из другого соединения эти данные не могут быть изменены до тех пор, пока блокировка не будет снята. Если запрос выполняется вне транзакции, то в нем могут быть считаны и заблокированные данные.
Блокировки устанавливаются в момент выполнения запроса, сбрасываются же при окончании транзакции. В случае если запрос выполняется вне транзакции предложение ДЛЯ ИЗМЕНЕНИЯ игнорируется.
В случае если после предложения ДЛЯ ИЗМЕНЕНИЯ отсутствуют имена таблиц, блокироваться будут считанные данные из всех таблиц, задействованных в запросе. В случае указания конкретных таблиц будут блокироваться только данные из перечисленных таблиц. Для блокировки можно указывать только таблицы верхнего уровня (т.е. не табличные части), участвующие в запросе. Должны приводиться именно имена таблиц, а не их псевдонимы, определенные в запросе. В случае указания виртуальной таблицы будут блокированы данные из всех таблиц, задействованных в виртуальной таблице. При указании виртуальной таблицы следует записывать ее имя без параметров.
Пример использования предложения ДЛЯ ИЗМЕНЕНИЯ можно посмотреть в типовой конфигурации "Управление торговлей" в модуле документа РеализацияТоваров, в функции СформироватьЗапросПоШапке(Режим), которая вызывается из обработчика проведения документа. В этой функции, в случае оперативного проведения выполняется запрос, в котором накладывается блокировка на регистр остатков:
Код 1C v 8.х
Использование транзакций при чтении данных
1.1. Если чтение данных из информационной базы должно быть ответственным, следует производить такое чтение в транзакции с предварительной установкой управляемых блокировок. В общем случае, ответственным следует считать любое чтение, на основе результатов которого производятся какие-либо изменения в информационной базе или принимаются решения.
Например, ответственное чтение данных требуется в следующих случаях:
- Чтение данных при проведении, для последующего формирования движений;
- Чтение данных для последующей целостной передачи в другую систему, например в программы типа «Клиент банк»;
- Выполнение групповой обработки объектов, при реструктуризации данных в обработчиках отложенного и оперативного обновления ИБ (*)
В некоторых случаях, ответственное чтение не требуется в силу решаемой прикладной задачи, например:
- Получение данных динамическими списками;
- Поиск данных;
- Формирование большинства отчетов.
Примечание: перед модификацией ссылочных объектов, обычно, следует устанавливать на них пессимистичные объектные блокировк
Неправильно:
Правильно:
В некоторых случаях, ответственное чтение не требуется, так как конкурентная работа с данными маловероятна или полностью исключена, например:
- Обращение к условно постоянной информации. Например, чтение константы ВалютаРегламентированногоУчета или обращение к учетной политике;
- Действия, которые гарантированно выполняются в монопольном режиме. Например, в процедурах обновления и первоначального заполнения данных информационной базы;
- Действия над данными, доступ к которым имеет только один пользователь, поэтому конкурентная работа с ними маловероятна или полностью исключена.
Например, персональные данные, хранящиеся в «разрезе» пользователей; - Мобильное приложение, где конкурентная работа с данными маловероятна или полностью исключена.
1.2. В большинстве случаев, при выполнении чтения в обработчиках событий связанных с модификацией данных, весь код обработчика выполняется в рамках системной транзакции, которая открыта платформой, и явно открывать новую транзакцию не требуется.
Например, в системной транзакции выполняются обработчики модулей объектов и соответствующие им подписки на события:
- ПередЗаписью;
- ПриЗаписи;
- ПередУдалением.
2. Выбор: исключительная или разделяемая блокировка
.2.1. Если в транзакции производится ответственное чтение данных с их последующим изменением, необходимо установить исключительную управляемую блокировку (до выполнения чтения). В противном случае возможно возникновение взаимоблокировки. Пример установки исключительной блокировки (без открытия транзакции – в предположении, что ранее уже была открыта системная транзакция):
2.2. Если в транзакции производится ответственное чтение данных без их последующего изменения (например, для формирования движений), необходимо установить разделяемую блокировку на читаемые данные и исключительную блокировку на изменяемые данные.Пример установки разделяемой блокировки (без открытия транзакции – в предположении, что ранее уже была открыта системная транзакция):
3. Избегать длинных транзакций.
3.1. Следует избегать длительных транзакций, которые выполняются длительное время. Чем дольше выполняется транзакция, тем большее время будут заняты ресурсы сервера 1С:Предприятия и СУБД, которые всегда ограничены и не могут эффективно использоваться для выполнения других задач.
Как правило, длинные транзакции отнимают на себя следующие ресурсы:
- В ходе выполнения транзакции все изменения в базе данных записываются в журнал транзакций, что необходимо для возможности откатить транзакцию.
- Блокировки, установленные в транзакции, остаются до конца транзакции (кроме разделяемых блокировок при чтении в блокировочных СУБД).
- На блокировочных СУБД, а также на сервере 1С:Предприятия блокировки занимают оперативную память;
- И другие ресурсы, необходимые самой бизнес-логике, которая выполняется в транзакции.
3.2. Длинные транзакции можно сократить за счет методов оптимизации запросов (см. статьи раздела «Оптимизация запросов»).
В отдельных случаях также рекомендуется разделять длинные транзакции на более мелкие порции, в пределах которых обеспечивается приемлемая целостность данных.
comol
Казалось о работе сервера 1С и механизма блокировок за годы разработки уже знаешь всё, но 1С не перестаёт удивлять. Механизм управляемых блокировок создан 1С для того чтобы по сути полноценно заменит механизм транзакционных блокировок на уровне сервера СУБД, отчасти из за политики "1С работает с любыми СУБД".
Итак начнём — простой кейс:
Конфигурация 2 объекта РС и Документ.
Регистр сведений независимый, код следующий:
Всё предельно просто.
Теперь Запускаем два сеанса.
Сеанс 1: Проводим документ с галкой "Блокировать". Ставим точку останова на строке:
"Если ЧитатьЗапросом Тогда".
Сеанс 2: Проводим документ с галкой "Читать запросом".
Внимание вопрос: "Проведётся ли документ во втором сеансе или нет?"
По всем канонам транзакционной разработки ответ конечно должен быть "Нет".
Тем не менее документ проводится — без особых проблем.
Повторяем эксперимент — только во втором сеансе ставим галку "чтение набора"
Тот же вопрос "Проведётся?"
Очевидный ответ — "Конечно", ведь по сути что там что там чтение всего регистра.
Тем не менее наблюдаем примерно следующую историю:
Таким образом, объектное чтение и чтение запросом работает по-разному. На мой взгляд это в корне неправильно. При этом объектное чтение, очевидно, учитывает управляемые блокировки, а чтение запросом — ни коим образом.
Сделаем несколько первых выводов:
Вывод 1: 1С полностью игнорирует все управляемые блокировки при чтении в транзакции запросом.
Вывод 2: Объектное чтение и чтение запросом работает по разному
Тут можно сказать здравствуй "фантомное чтение", "неповторяющееся чтение". От "Грязного чтения" нас убережет MS SQL: в уровне изоляции READ COMMITED SNAPSHOT "грязное чтение" невозможно. Хотя я тоже сомневаюсь — потому как объектная модель 1С не полностью отражается в СУБД возможно могут быть нюансы, но примеров подобрать пока не удаётся.
Для того чтобы понять на каком этапе своего развития в платформе 1С потеряли требование "согласованности данных" обратимся к истории.
Попробуем выполнить следующий код в разных версиях платформы:
Я специально избегаю регистров накопления в примерах, потому как в РН как минимум две таблицы на уровне СУБД, а хочется продемонстрировать на одной.
Этот код выполняется в транзакции — в обработке проведения документа в моём случае. Первую транзакцию нужно конечно остановить и попытаться провести вторую. По условию мы не должны уйти в минус
1С 8.1 и ранее (ну или просто Автоматический режим блокировки)
второй документ "висит на блокировке". После прохождения точки останова второй документ проводится, запись в РС не создаётся.
Код отрабатывает правильно как в случае остановки "после записи" так и в случае "после чтения".
Секрет конечно в инструкции "для изменения", которая кроме всего прочего превращает S блокировку в U.
На уровне СУБД уровень изоляции MS SQL — SERIALIZABLE. MS SQL блокирует всё что надо и не надо. Чтобы получить несогласованные данные естественно надо очень постараться, и то скорее всего не получится.
На с параллельной работой в таком варианте конечно будут трудности, и всем давно известно какие.
1С 8.2 и первые версии 8.3 (режим управляемых блокировок)
Появились "Управляемые блокировки". Самое главное что происходит с MS SQL при установке "управляемой блокировки" — уровень изоляции становится "READ COMMITED".
Чтобы включить его в последних редакциях 8.3 нужно выполнить:
ALTER DATABASE databasename
SET ALLOW_SNAPSHOT_ISOLATION OFF
GO
ALTER DATABASE databasename
SET READ_COMMITTED_SNAPSHOT OFF
GO
Здесь уже немного похуже. Если точку останова в коде поставить после записи — всё отработает как и в предыдущем примере — транзакция установит X блокировку и всё будет OK. А вот если точку останова поставить после чтения — получим совместимые S блокировки — данные будут считаны дважды, соответственно
Конечно данный уровень изоляции уже не охраняет нас от ошибок "фантомного" и "неповторяемого" чтения.
В приведённом примере, тем не менее в большинстве случаев всё будет работать верно, потому что всё-таки поведение системы в транзакции более-менее прогнозируемо — если данные считаны, то на них устанавливается как минимум S блокировка.
Конечно нужно добавить в этот код начало вроде:
и нам уже ничего не страшно.
Зачем я тогда делаю на этом акцент?
В данном примере вы читаете остатки (по сути) — их конечно нужно блокировать.
Но если у вас в транзакции происходит чтение справочника, который в данный момент может писать другая транзакция?
Вот тут как раз S блокировка очень сильно пригодилась бы. Но об этом дальше
Современные версии 1С 8.3
Возвращаем обратно режим версионника:
ALTER DATABASE databasename
SET ALLOW_SNAPSHOT_ISOLATION ON
GO
ALTER DATABASE databasename
SET READ_COMMITTED_SNAPSHOT ON
GO
Выполняем код — получаем "-1" как в случае установки "точки останова" при чтении, так и в случае установки "точки останова" при записи.
Теперь можно сделать ещё один вывод:
Вывод 3: чем современнее версия 1С, тем больше возможности для параллельной работы и тем меньше шансов обеспечить согласованность данных.
А теперь "Гвоздь программы": Файловая база — код приведенный в блоке кода 1 выполняем в ней. Единственное изменение — код чтения данных из регистра придётся перенести в обработку, т.к. в файловой базе два одинаковых документа параллельно провести не получится при всём желании.
В обработке будет код:
В документе ставим галку "блокировать" и точку останова, выполняем обработку — уверенно висит на блокировке.
Убираем галку "блокировать" в документе (но не точку останова) конечно без проблем читает регистр.
Итак, в завершение "чудес" управляемых блокировок получаем их разную работу в файловой и клиент-серверной версии.
Вывод 4: Управляемые блокировки в файловой и клиент-серверной версиях работают по-разному. В файловой — "нормально".
Теперь о косяках в типовых. Для примера возьму УТ 11 — "ближе к телу" она.
Перед записью в набор движения естественно читаются, происходит это в функции "ТекстЗапросаТаблицаТоварыНаСкладах(Запро с, ТекстыЗапроса, Регистры)" — для Товаров на складах.
Так вот, "косяк" потенциально будет во всех строчках этого запроса где точка фигурирует дважды:
"КОГДА ЕСТЬNULL(ТаблицаТовары.Назначение.Движен ияПоСкладскимРегистрам, ЛОЖЬ)"
"И (НЕ ТаблицаТовары.Склад.ИспользоватьОрдерную СхемуПриОтгрузке
И ТаблицаТовары.Номенклатура.ТипНоменклату ры В"
Что в этом плохого?
Ну вот представьте — начали вы проведение документа пока склад ещё был не ордерным, а в процессе проведения кто-то его поменял на ордерный.
Или тип номенклатуры сменил с "товара" на "услугу". Он это сможет влёгкую сделать. А вы потом не сможете доказать что "когда я нажимал кнопку всё было хорошо". Все проверки, включая конечно "обработку проверки заполнения" документ пройдёт ещё до изменения данных.
Кроме того — есть прекрасная возможность в один регистр записать данные исходя из соображений что склад ордерный, а в другой — нет. Это и есть так называемое "фантомное чтение", которое, как мы помним, для "READ COMMITED SNAPSHOT" вполне возможно.
Наверное не нужно лишний раз писать что найти и устранить подобные ошибки очень и очень сложно. Ещё труднее понять что же являлось причиной подобного поведения системы.
"Эти справочники защищены от изменений, не так всё просто" — напрашивается комментарий. На самом деле всё просто. Распределенная база, полный обмен. Задано небольшое число элементов в транзакции. В каждом справочнике есть код:
Который отключает все эти проверки. В удаленной базе они могли быть пройдены, но на тот момент в ней не было актуальной копии документов, или вообще не было и не будет этих документов.
Для корректной работы проведения документа нужно чтобы на все считываемые неявным соединением таблицы накладывались разделяемые блокировки.
К слову, реально это нужно только для проведения — в других случаях необходимость что-то блокировать сомнительна.
Вывод 5: В типовых конфигурациях не учитывается необходимость блокировать записи таблиц с которыми происходит неявное соединение при проведении
Что делать я думаю понятно? Если есть параллельная работа в базе — реально параллельная по хорошему при проведении документа нужно накладывать ещё кучу разделяемых блокировок, которые помогут избежать подобных ситуаций.
Почему на это не особенно обращают внимание? Да потому что эти ошибки видны только в случае реальной параллельной работы большого количества пользователей, да и то крайне редки. Если у вас много пользователей в базе, у вас скорее всего наберётся несколько куч проблем более актуальных чем описанный выше кейс. Ну а у кого всё хорошо и все вопросы решены скорее всего уже правильно расставлены блокировки, и такие крупные компании редко работают на типовых базах. Но вообще знать о таком "поведении" платформы наверное нужно и учитывать его в проектах, которые ориентированы на большое количество параллельно работающих пользователей. Потому как пока мы (1С-ники) не научимся корректно работать в системах с 1000+ активных пользователей и обеспечивать в них согласованность данных SAP нам на рынке не подвинуть.