При работе 1С:Предприятия 8 в клиент-серверном варианте используется режим блокировки на уровне записей таблицы базы данных, в отличие от файл-серверного варианта, в котором блокировка устанавливается на всю таблицу. Это означает, что в клиент-серверном варианте при выполнении транзакции блокироваться будут только те записи, которые необходимы для выполнения текущей операции. Это позволяет увеличить параллельность работы пользователей в конкурентном режиме (например - при вводе документов) и, тем самым, увеличить пропускную способность системы в целом.
Однако использование преимуществ данного режима блокировки требует более внимательного проектирования прикладного кода, осуществляющего доступ к данным в транзакции (например, процедуры обработки проведения документа). В противном случае можно получить эффект прямо противоположный – пропускная способность системы уменьшится. Основной причиной этого является появление взаимных блокировок (deadlocks) при конкурентном доступе к данным со стороны нескольких сессий 1С:Предприятия 8.
Взаимная блокировка (deadlock) – это ситуация, когда транзакции блокируют друг друга таким образом, что дальнейшее выполнение их становится невозможно. Ни одна из участвующих во взаимной блокировке транзакций не может отпустить уже захваченные ей ресурсы до того, как наложит блокировки на все ресурсы, которые ей необходимы для завершения. Получить все необходимые ресурсы мешают уже наложенные другой транзакцией блокировки. Таким образом, получается замкнутый круг. Естественно, и транзакций, и объектов в общем случае может быть сколь угодно много. Разрешить подобную ситуацию без внешнего вмешательства невозможно, и если не предпринимать специальных усилий, то транзакции будут находиться в состоянии ожидания бесконечно долго. Разрешить подобную ситуацию можно лишь путем отмены хотя бы одной из транзакций и в MS SQL Server имеются механизмы определения подобных тупиковых ситуаций и их устранения.
При выборе для отмены одной из участвующих во взаимной блокировке транзакции, MS SQL Server руководствуется следующими соображениями:
Самостоятельно Microsoft SQL Server отмененную транзакцию заново не запускает, а возвращает сообщение об ошибке. В клиентском приложении, при необходимости, можно предусмотреть обработку данной ситуации и, возможно, перезапуск отмененной транзакции.
Однако следует понимать недостатки данного решения:
Для того чтобы избежать подобных ситуаций следует минимизировать вероятность возникновения взаимных блокировок.
Рассмотрим два типичных случая, которые могут приводить к взаимным блокировкам:
Последовательность действий, приводящая к взаимной блокировке:
Обнаружив такую ситуацию, Microsoft SQL Server принудительно отменяет одну из транзакций. Другая транзакция завершается нормально.
Избежать подобной ситуации можно обеспечив единую последовательность работы с ресурсами во всех транзакциях, т.е. сначала запись регистра Р1, затем Р2. В этом случае транзакция Т2 не смогла бы заблокировать ресурсы, необходимые транзакции Т1 для завершения, и взаимной блокировки не возникло бы, поскольку транзакции Т2 пришлось бы ожидать завершения транзакции Т1. При использовании автоматического режима записи движений документа в модуле проведения (набор записей регистра, в котором регистрируются движения документа, созданный в процедуре проведения документа, будет записан системой автоматически, если он не был записан с помощью метода Записать() в явном виде) единая последовательность обращения к регистрам поддерживается системой. В случае, если запись движений документа осуществляется в явном виде, непосредственно в процедуре проведения, необходимо самостоятельно обеспечить единую последовательность обращения к регистрам в процедурах проведения всех документов во избежание появления взаимных блокировок при одновременной записи и проведении документов различными сессиями 1С:Предприятия 8.
Последовательность действий, приводящая к взаимной блокировке:
Можно заметить, что этот процесс никогда бы не закончился, если бы одна из транзакций не была отменена Microsoft SQL Server принудительно.
Избежать подобной ситуации можно, используя при выполнении запроса к таблице остатков регистра Р1 оператор "ДЛЯ ИЗМЕНЕНИЯ". В этом случае на прочитанные записи будет установлена блокировка более высокого уровня - блокировка обновления. Такая блокировка совместима с разделяемой, что позволит транзакциям, осуществляющим чтение данных, на которые установлена блокировка обновления, обращаться к этим данным беспрепятственно. А когда понадобится их обновить, то проблем быть не должно, так как блокировки обновления между собой несовместимы, и, значит, другие транзакции, читающие эти данные для последующего изменения (и естественно тоже запросившие их с блокировкой обновления), будут ждать, пока эти данные поменяются, не препятствуя другим сессиям.