В настоящей статье рассматриваются типичные причины возникновения избыточных (не обусловленных бизнес-логикой приложения) блокировок и методы их устранения.
Блокировка данных - это механизм поддержания целостности данных при работе в многопользовательской среде. Подробная информация о том, для чего нужны блокировки и как они реализованы в 1С:Предприятии 8, содержится в статье "Блокировки данных в 1С:Предприятии 8". Рекомендуется предварительно ознакомится с этим материалом для лучшего понимания настоящей статьи.
В настоящей статье будут рассмотрены типичные причины возникновения избыточных (не обусловленных бизнес-логикой приложения) блокировок и методы их устранения.
Значительная часть проблем, связанных с избыточными блокировками, может быть обнаружена путем анализа кода конфигурации и структуры метаданных. Имеется перечень типичных ошибок в коде и структуре данных, последствия которых достаточно хорошо изучены и легко предсказуемы. Анализ кода с использованием этого перечня позволяет решить большую часть проблем с блокировками, не углубляясь в детальную техническую информацию (текст запроса на языке SQL, системная информация о блокировке на уровне СУБД и т.д.).
Основные причины возникновения избыточных блокировок, диагностируемые на уровне кода конфигурации и структуры метаданных:
В настоящей статье рассматриваются перечисленные причины возникновения избыточных блокировок и даются рекомендации по оптимизации приложения.
Неоптимальная работа запроса может влиять как на скорость работы данного запроса, так и на производительность работы других пользователей. Это связано с тем, что при неоптимальной работе запроса блокируются избыточные данные, в результате чего другие пользователи не могут выполнять свои запросы к этим данным из-за ожиданий на блокировках.
Эта причина возникновения избыточных блокировок в многопользовательской системе является наиболее часто встречающейся. Поэтому, первое, что следует сделать – проанализировать запросы, которые выполняются в проблемной строке кода и при необходимости оптимизировать их.
Рекомендации по оптимизации запросов содержатся в статье "Типичные причины неоптимальной работы запросов и методы оптимизации".
Проектные ошибки при выборе и проектировании структуры того или иного объекта метаданных для реализации прикладной функциональности, могут привести к большому количеству избыточных блокировок и как следствие к серьезному падению общей производительности системы.
Ожидание на блокировке данных происходит в том случае, если две различные сессии 1С:Предприятия пытаются захватить один и тот же ресурс. При работе с разными ресурсами ожидание на блокировке не происходит. В данном контексте термин «ресурс» используется в качестве обозначения неделимой совокупности данных, которая блокируется (или не блокируется) только вся целиком.
Таким образом, вопрос сводится к тому, какие именно ресурсы захватываются при выполнении того или иного действия с данными. Или, иначе говоря, насколько «мелко нарезаны» данные 1С:Предприятия.
При анализе структуры метаданных следует обратить внимание на следующие объекты:
Ниже даны рекомендации по анализу и оптимизации каждого из перечисленных объектов. Объекты перечислены в порядке убывания «потенциальной опасности» для параллельности системы.
Не рекомендуется использовать константы для хранения постоянно меняющихся данных. Это приведет к возникновению избыточных блокировок и снижению общей производительности системы. Для хранения таких данных можно использовать другие объекты метаданных 1С:Педприятия (справочники, регистры и т.д.).
Константы предназначены для хранения настроек, которые могут часто считываться, но не должны часто изменяться. Значения всех констант хранятся в одном ресурсе. Это означает, что при блокировке одной константы будут заблокированы все константы.
Перед программистами была поставлена задача вести общий счетчик количества документов, введенных в систему. Для реализации этой задачи была создана константа «КоличествоДокументов» и в процедуры «ОбработкаПроведения» каждого документа был вписан следующий код:
Копировать в буфер обменаКонстанты.КоличествоДокументов.Установить(Константы.КоличествоДокументов.Получить() + 1);
Этот код выполняется в обработчике проведения документа, то есть в транзакции. Транзакция включает в себя множество сложных вычислений и операций с данными, которые производятся при проведении документа. Вследствие этого транзакция может выполнятся достаточно длительное время. При записи данной константы блокируются все константы (то есть становятся невозможными другие операции записи любых констант) причем эта блокировка удерживается до конца транзакции. Таким образом, в системе возникает избыточная блокировка, которая делает невозможной параллельную работу пользователей.
Подобное решение способно полностью парализовать оперативную работу многопользовательской системы.
Не рекомендуется двигать границу последовательности при проведении документов. Это может привести к возникновению ожиданий на блокировках и снижению общей производительности системы. Операцию движения границы последовательности следует вынести из оперативных операций в регламентные, например выполнять регламентной обработкой с заданной частотой.
Граница последовательности по одному набору значений измерений является одним ресурсом. Это означает, что при движении границы последовательности по одному набору значений измерений разные пользователи будут пытаться захватить один и тот же ресурс, то есть будут блокировать друг друга.
При нормальной работе последовательность должна двигаться постоянно вперед (иначе не имеет смысла её двигать). Сбиваться назад последовательность должна редко, и после сбития последовательность уже не двигается. Не рекомендуется при проведении документов или формировании отчетов восстанавливать последовательность. Это следует делать регламентными операциями.
В системе, построенной на базе УПП, используется учетная политика, предполагающая вычисление себестоимости списываемых товаров в оперативном режиме (непосредственно в момент списания).
Алгоритмы партионного учета в УПП используют последовательность «Партионный учет», имеющую одно измерение: «Организация».
Для вычисления себестоимости списываемых товаров при проведении расходного документа необходимо переместить границу последовательности для данной организации на момент времени проводимого документа. Если два пользователя будут одновременно проводить расходные документы по одной организации (что весьма вероятно), то они будут блокировать друг друга. Такое поведение системы не является особенностью реализации последовательностей в 1С:Предприятии, но продиктовано требованиями самого алгоритма – необходимо знать точную последовательность расположения документов. То есть все конкурирующие по времени (одновременно проводящиеся) документы должны выстроиться друг за другом.
Движение границы последовательности при оперативном проведении документов способно значительно снизить общую производительности системы. В данном случае правильным решением было бы выключение этого флажка, то есть отказ от оперативного расчета себестоимости при проведении документов. Вместо этого следует использовать регламентную обработку, входящую в состав УПП, которая будет вычислять себестоимость с некоторой заданной частотой.
Если в системе осуществляется оперативная запись движений по бухгалтерскому регистру в многопользовательском режиме, то рекомендуется включить для данного регистра режим разделения итогов. При включенном режиме разжеления итогов пользователи смогут параллельно обновлять таблицу остатков даже в том случае, если у них совпадает период, счет и значения измерений. В противном случае таблица остатков регистра бухгалтерии может стать узким местом при конкурентной работе большого количества пользователей.
Обратите внимание на то, что режим разделения итогов обеспечивает параллельность работы только при записи движений. Если необходимо считывать остатки (например, для контроля остатков), то запросы чтения будут блокироваться при совпадении периода, счета и значений измерений. Исключить эту блокировку технически невозможно, но можно уменьшить ее влияние на общую производительность системы. Для этого рекомендуется выполнять запросы чтения остатков как можно позже, то есть максимально близко к концу транзакции. Методика переноса контроля остатков в конец транзакции более подробно обсуждается ниже.
В конфигурации определен регистр «Хозрасчетный» с измерениями «Организация» и «Валюта».
При этом запрещено разделение итогов регистра:
Предположим, что два пользователя одновременно проводят документы, которые осуществляют движение по данному регистру.
Пользователи будут блокировать друг друга в том случае, если движения:
В реальной жизни одновременное выполнение перечисленных условий является весьма вероятным, поскольку большинство пользователей будет работать в одном периоде, с одним счетом и с одинаковыми значениями измерений (организация и валюта). Это может привести к возникновению ожиданий на блокировках и снижению общей производительности системы.
Для решения этой проблемы следует включить режим разделения итогов (см. следующий пример).
Если для этого же регистра разрешить и включить режим разделения итогов, то ситуация изменится.
Разрешим режим разделения итогов (в режиме конфигурирования):
Включим режим разделения итогов (в режиме 1С:Предприятия):
После этого конкурирующие пользователи смогут параллельно записывать движения по регистру даже в том случае, если совпадают период, номер счета и значения всех измерений.
Однако, если при этом осуществляется контроль остатков по данному регистру, то эффекта от включения режима разделения не будет (см. пример 3).
Если при записи набора записей в регистр осуществляется контроль остатков (например, с целью недопущения получения отрицательных остатков), то режим разделения итогов не даст никакого эффекта (то есть, параллельности системы не повысится). Предположим, что в процедуре «ПередЗаписью» модуля набора записей регистра бухгалтерии «Хозрасчетный» выполняется следующий запрос:
Копировать в буфер обменаЗапрос.Текст = "ВЫБРАТЬ | СуммаОстаток, | СуммаОстатокДт, | СуммаОстатокКт |ИЗ | РегистрБухгалтерии.Хозрасчетный.Остатки(&Период, &Счет, , Организация = &Организация)";
При выполнении этого запроса будут прочитаны (и заблокированы от записи) остатки по указанному условию для всех пользовательских подключений. То есть, разные ресурсы (созданные режимом разделения итогов) будут как бы объединены в один. По этой причине параллельность останется такой же, как если бы режим разделения итогов не был включен.
Для того, чтобы минимизировать влияние этой блокировки на общую производительность системы, рекомендуется перенести ее как можно ближе к концу транзакции. Например, можно вынести эту проверку в модуль документа в обработчик события "ПриПроведении" после записи (в явном виде) всех движений по всем регистрам.
При проектировании регистра накопления следует помнить, что остатки по одному набору измерений хранятся в одном ресурсе регистра. То есть, степень параллельности при работе с регистром фактически зависит от состава его измерений. Состав измерений необходимо подбирать в соответствии с тем, насколько мелко должны быть «нарезаны» остатки, исходя из прикладной функциональности системы.
В том случае, если состав измерений не позволяет обеспечить необходимую параллельность при работе с регистром, можно использовать режим разделения итогов аналогично тому, как это было показано для регистров бухгалтерии. Следует учитывать, что режим разделения итогов не обеспечит параллельность при контроле остатков по регистру. Если контроль остатков необходим, то следует перенести его как можно ближе к концу транзакции.
Рассмотрим в качестве примера регистр накопления «ТоварыНаСкладах» со следующим составом измерений:
Предположим два пользователя одновременно проводят документы, которые записывают движения в данный регистр накопления. При этом первый пользователь пишет следующий набор записей:
№ | Склад | Номенклатура |
---|---|---|
1 | Основной склад | Кресло-качалка |
2 | Основной склад | Кухонный гарнитур "Тинга-2" |
3 | Склад №2 | Мебельный гарнитур "Торэ" |
Второй пользователь пишет в этот же регистр следующий набор записей:
№ | Склад | Номенклатура |
---|---|---|
1 | Основной склад | Мебельный гарнитур "Торэ" |
2 | Склад №2 | Кресло-качалка |
Эти наборы записей не содержат строк, совпадающих по значениям всех измерений, поэтому ожидание на блокировке в данном случае не возникнет.
Предположим, что второй пользователь записывает следующий набор записей
№ | Склад | Номенклатура |
---|---|---|
1 | Основной склад | Кухонный гарнитур "Тинга-2" |
2 | Склад №2 | Кресло-качалка |
2 | Оптовый склад | Спальный гарнитур "Инга-М" |
Этот набор записей содержит строку (№1), совпадающую по значениям всех измерений со строкой набора записей первого пользователя (№2).
В этом случае возникнет ожидание на блокировке и один из пользователей будет дожидаться окончания операции другого пользователя, то есть общая производительность системы снизится.
Регистр накопления так же поддерживает режим разделения итогов, который позволит в данной ситуации избежать блокировки (см. Пример 3).
Если для данного регистра накопления включен режим разделения итогов, то будет возможна параллельная запись двух наборов, даже в том случае если они содержат одинаковые (по значениям измерений) строки. Однако, если при этом используется контроль остатков, то режим разделения итогов не даст положительного эффекта. Иначе говоря, режим разделения итогов для регистра накопления работает аналогично регистру бухгалтерии. Для снижения влияния этой блокировки на общую производительность системы нужно перенести контроль остатков как можно ближе к концу транзакции.
Режим разделения итогов эффективно решает задачу исключения блокировки при записи остатков регистров бухгалтерии и накопления. Однако, для решения некоторых задач необходимо выполнять блокирующее чтение итогов. Классическим примером такой задачи является контроль остатков при проведении документа. Если в результате проведения документа остатки станут отрицательными, то транзакция должна быть отменена (проводить такой документ нельзя).
Операция чтения остатков должна быть блокирующей, то есть необходимо запретить двум пользователям одновременно читать один и тот же остаток. Если чтение будет неблокирующим, то возможна ситуация, при которой два пользователя одновременно прочитают один и тот же остаток (например 10 единиц) и примут решение о возможности списания части этого остатка. Если сумма списаний двух пользователей будет больше 10, то в итоге остаток получится отрицательным. Например, первый пользователь спишет 8 единиц (8 меньше 10, следовательно операция разрешена), а второй пользователь спишет 6 единиц (на таком же основании). Результатом будет -4 единицы остатка, что недопустимо с точки зрения прикладной логики системы.
Итак, существуют задачи, для решения которых необходимо выполнять блокирующее чтение остатков. Эта блокировка не может быть устранена, т.к. это привело бы к нарушению логики работы системы. Однако, можно уменьшить ее влияние на интегральную производительности системы. Для этого рекомендуется изменить стандартный подход к контролю остатков. Обычно, для контроля остатков используется запрос в модуле набора записей регистра, который идет перед записью набора. При этом возможны следующие проблемы:
Для того, чтобы минимизировать влияние блокирующего чтения остатков на производительность системы необходимо:
Выгрузка изменений по плану обмена включает в себя этап считывания всех измененных записей из таблицы регистрации изменений. Запрос, выполняемый в транзакции, отрабатывает условие "все измененные объекты данного типа", поэтому на время работы транзакции блокируется возможность регистрации новых изменений для объектов данного типа. Это означает, что во время выгрузки изменений другие пользователи не смогут вносить новые изменения в объекты данного типа. Если к моменты выгрузки было зарегистрировано большое количество изменений, то выгрузка может занимать значительное время, что может привести к существенному снижению общей пропускной способности системы, появлению ошибок типа таймаут и т.п.
Для исключения этих блокировок рекомендуется осуществлять массивные выгрузки данных в моменты минимальной загрузки системы, например, в ночное время. Другое возможное решение проблемы - делать выгрузки достаточно часто. В этом случае объем выгружаемых данных будет незначительным, выгрузка будет осуществляться быстро и не окажет существенного влияния на работу других пользователей.
При работе в автоматическом режиме управления блокировкой 1С:Предприятие устанавливает высокую степень изоляции данных в транзакции на уровне СУБД. Это позволяет полностью исключить возможность получения нецелостных или некорректных данных без каких-либо специальных усилий со стороны прикладных разработчиков.
Однако, при этом могут возникать некоторые избыточные блокировки на уровне СУБД. Эти блокировки связанны как с особенностями реализации механизмов блокировок в самой СУБД, так и с тем, что СУБД не может учитывать (и не учитывает) физический смысл и структуру объектов метаданных 1С:Предприятия.
Для исключения этих блокировок рекомендуется перевести конфигурацию (или ее часть) в управляемый режим блокировки данных в транзакции. Обратите внимание на то, что такой перевод может потребовать доработки исходного кода приложения.
Подробная информация о методике перевода приложений в управляемый режим блокировки содержится в статье "Блокировки данных в 1С:Предприятии 8".
При изменении любых данных информационной базы, 1С:Предприятие автоматически открывает транзакцию для того, чтобы контролировать целостность изменений данных. Например, если вы напишете "Документ.Записать()", то все изменения данных в рамках этой операции (запись документа и его табличных частей, запись движений и обновление итогов регистров и т.д.) будут выполнены в транзакции, которую автоматически откроет 1С:Предприятие.
Иногда необходимо выполнить согласованные изменения нескольких объектов, например записать два зависимых документа, которые имеют смысл только вместе. Для того, чтобы поддержать целостность таких изменений необходимо открыть транзакцию в явном виде при помощи команды "ОткрытьТранзакцию()". При этом следует внимательно следить за тем, чтобы длительность транзакции была минимальной. Оптимальная длительность транзакции - несколько секунд. Транзакция, меняющая взаимозависимые данные, и длящаяся более 10 секунд может создать большие проблемы другим пользователям, поэтому для данного случая следует реализовать контроль целостности изменений каким-то другим способом (на прикладном уровне, а не на уровне платформы).
Типичные примеры операций, которые не следует выполнять в одной большой транзакции: