Блокирование и разблокирование. Блокировка базы данных и самостоятельное резервное копирование

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

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

Оба типа управления одновременным доступом к данным используют в своей работе блокировки данных. Блокировка - это объект, с помощью которого показывается зависимость пользователя от какого-либо ресурса. Система управления блокировками запрещает другим пользователям выполнять над ресурсами операции, негативно влияющие на зависимость пользователя, владеющего блокировкой, от этого ресурса. Блокировка создает временное ограничение на выполнение некоторых операций обработки тех данных, от которых зависит пользователь. Основные типы блокировок:

  • разделяемая (shared, S) - накладывается при выполнении операций чтения данных (например, SELECT). Никакая другая транзакция не сможет изменить или удалить данные, если на них установлена разделяемая блокировка. Разделяемая блокировка обычно освобождается после завершения чтения данных, но если только уровень изоляции транзакции установлен в REPEATABLE READ или выше, то разделяемая блокировка сохраняется до завершения транзакции.
  • монопольная (exclusive, X) - накладывается при выполнении операций изменения данных (например, UPDATE). Никакая другая транзакция не сможет ни изменить, ни даже прочитать данные, если на них установления монопольная блокировка. Исключение: прочитать данные с установленной монопольной блокировкой возможно, если только уровень изоляции транзакции установлен в READ UNCOMMITTED. Монопольная блокировка освобождается после завершения транзакции.

Для просмотра установленных блокировок в Microsoft SQL Server используется системная хранимая процедура sp_lock.
Транзакция - последовательность операций, выполняемая как целостная логическая единица работы. Свойства транзакции:

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

Оператор BEGIN TRAN используется для начала транзакции. Для завершения транзакции используется либо оператор COMMIT TRAN (используется в том случае, если транзакция завершается успешно и все действия, выполнении в рамках транзакции должны быть сохранены в базе данных), либо оператор ROLLBACK TRAN (используется в том случае, если транзакция завершается с ошибками и все действия, выполнении в рамках транзакции должны быть отменены). Синтаксис транзакции выглядит так:

BEGIN TRAN
{операторы SQL}

{если ошибок не было}
COMMIT TRAN
{иначе, если возникли ошибки}
ROLLBACK TRAN

Управление транзакциями осуществляется на уровне соединения, а не пользователя. После начала транзакции в соединении и до ее успешного или неуспешного завершения все выполненные в этом соединении операторы SQL становятся частью транзакции.

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

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

Для управления взаимодействия транзакций между собой и, соответственно, управлением блокировками данных, возникающими при выполнении транзакций, используется установка уровня изоляции транзакции. Уровень изоляции транзакции определяет, какие блокировки накладываются на данные, обрабатываемые в рамках транзакций. Оператор SET TRANSACTION ISOLATION LEVEL используется для изменения уровня изоляции транзакции.

Синтаксис оператора SET TRANSACTION ISOLATION LEVEL выглядит так:

SET TRANSACTION ISOLATION LEVEL {уровень изоляции}

В языке SQL допустимы следующие уровни изоляции транзакции:

  • READ UNCOMMITTED (неподтвержденное чтение) - транзакция с этим уровнем изоляции может читать записи, которые были изменены, но еще не зафиксированы другой транзакцией.
  • READ COMMITTED (подтвержденное чтение) - транзакция с этим уровнем изоляции может читать только те записи, которые были изменены и уже зафиксированы другой транзакцией.
  • REPEATABLE READ (повторяемое чтение) - транзакция с этим уровнем изоляции может читать только те записи, которые были изменены и уже зафиксированы другой транзакцией, и никакая другая транзакция не может изменить записи, которые были прочитаны в рамках этой транзакции.
  • SERIALIZABLE (упорядочение) - транзакция с этим уровнем изоляции может читать только те записи, которые были изменены и уже зафиксированы другой транзакцией; никакая другая транзакция не может изменить записи, которые были прочитаны в рамках этой транзакции и никакая другая транзакция не может добавить или удалить записи из диапазона записей, прочитанного в рамках этой транзакции.

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

Блокирование и разблокирование

Если Вы предпочитаете использовать какие-то другие утилиты для создания резервных копий базы данных или просто делать обычную копию базы данных как резервную, то в игру вступает режим блокировки/разблокировки программы nbackup . «Блокировка » в данном случае означает, что основной файл базы данных временно замораживается, а не невозможность внесения изменений в базу данных. Как и в режиме резервирования, изменения фиксируются во временном файле дельты; при разблокировании файл дельты объединяется с основным файлом базы данных.

В качестве напоминания: nbackup.exe находится в подпапке bin папки, куда установлена СУБД Firebird. Типичными местонахождениями, например, являются C:\Program Files\Firebird\Firebird_2_0\bin (Windows) или /opt/firebird/bin (Linux). У утилиты нет графического интерфейса; Вы запускаете ее из командной строки (или из пакетного файла, или из другого приложения).

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

Типичным сценарием является следующий:

    Блокировать базу данных с помощью параметра -L (Lock):

    nbackup [-U <пользователь> -P <пароль> ] -L <база_данных>
  1. Теперь можно создать резервную копию, сжать файл базы данных (и много еще чего можно делать с содержимым файла базы данных), используя Ваши любимые программы. Простое копирование файла также допустимо.

    Разблокировать базу данных с помощью параметра -N (uNlock):

    nbackup [-U <пользователь> -P <пароль> ] -N <база_данных>

Последняя команда также приведет к объединению файла дельты, куда записывались все изменения (мета)данных за время блокировки, с основным файлом базы данных.

Созанная резервная копия будет содержать данные, которые являлись актуальными на момент начала блокировки; эти данные не зависят от длительности блокировки, а также от периода времени, прошедшего с начала блокировки до момента начала резервного копирования.

Внимание

Что применимо к резервированию/восстановлению, также применимо и к блокированию/разблокированию: не используйте блокирование/разблокирование на многофайловых базах данных. Пока ситуация не изменится, не подпускайте nbackup к многофайловым базам данных!

Восстановление из резервной копии, сделанной после выполнения "nbackup -L"

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

  1. Разархивируйте/скопируйте/восстановите файл базы данных с помощью используемых Вами утилит.

    Теперь разблокируйте базу данных, но не с параметром -N , а с параметром -F (Fixup):

    nbackup -F <база_данных>

Почему существует два параметра командной строки, -N и -F ?

  • При использовании параметра -N сначала определяется наличие любых изменений с момента блокирования базы данных (после использования параметра -L ) и производится объединение временного файла дельты и основного файла базы данных. После этого база данных переводится в нормальный режим чтения/записи, а временный файл удаляется.

    При использовании параметра -F только изменяется в «нормальное » значение флага состояния самостоятельно восстановленной базы данных.

Итак, Вы используете:

    параметр -N после создания резервной копии своими силами (для возвращения флага состояния после ранее выполненного блокирования файла с параметром -L );

    параметр -F после самостоятельного восстановления из такой резервной копии.

Замечание

Не очень хорошо получилось, что последний параметр -F назван по слову Fixup (поправить): его предназначение не исправлять что-либо, а только разблокировать базу данных. Параметр -N (uNlock, разблокировать), с другой стороны, не только разблокирует базу данных, но и вносит в нее некоторые правки (внедряет сделанные изменения в базу данных). Однако, нам придется работать с тем, что есть.

Сегодня речь пойдет о блокировках как на уровне 1С 8.3 и 8.2, так и на уровне СУБД. Блокировка данных — обязательный элемент любой системы, количество пользователей в которой больше одного.

Ниже я распишу, как работают блокировки, и каких типов они бывают.

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

Вред системе могут принести только избыточные («лишние») блокировки, это те блокировки, которые блокируют лишнюю информацию. Такие блокировки необходимо научиться устранять, они могут привести к неоптимальной работе системы.

Блокировки в 1С делятся условно на объектные и транзакционные.

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

Объектные блокировки 1С

Данный вид блокировок полностью реализован на уровне платформы 1С и никак не затрагивает СУБД.

Получите 267 видеоуроков по 1С бесплатно:

Пессимистические блокировки

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

Оптимистические блокировки

Данная блокировка сравнивает версии объекта: если два пользователя открыли форму, и один из них изменил и записал объект, то второму при записи система выдаст ошибку, что версии объектов отличаются.

Транзакционные блокировки 1С

Механизм тразакционных блокировок 1С гораздо интереснее и более функционален, чем механизм объектных блокировок. В этом механизме активно участвуют блокировки на уровне СУБД.

Неверная работа транзакционных блокировок может привести с следующим проблемам:

  • проблема потерянного изменения;
  • проблема грязного чтения;
  • неповторяемость чтения;
  • чтение фантомов.

Подробно эти проблемы были рассмотрены в статье об .

Автоматические транзакционные блокировки 1С и СУБД

В автоматическом режиме работы за блокировки целиком и полностью отвечает СУБД. Разработчик в данном случае абсолютно не участвует в процессе. Это облегчает труд программиста 1С, однако создание информационной системы для большого количества пользователей на автоматических блокировках нежелательно (особенно для СУБД PostgreSQL, Oracle BD — при модификации данных они полностью блокируют таблицу).

Для разных СУБД в автоматическом режиме используются разные степени изоляции:

  • SERIALIZABLE на всю таблицу – файловый режим 1С, Oracle;
  • SERIALIZABLE на записи – MS SQL, IBM DB2 при работе с не объектными сущностями;
  • REPEATABLE READ на записи – MS SQL, IBM DB2 при работе с объектными сущностями.

Управляемые режим транзакционных блокировок 1С и СУБД

В всю ответственность на себя берет разработчик прикладного решения на уровне 1С. При этом СУБД устанавливает достаточно высокий уровень изоляции для транзакций — READ COMMITED (SERIALIZABLE для файловой СУБД).

При выполнении любой операции с БД менеджер блокировок 1С анализирует возможность блокировки (захвата) ресурса. Блокировки одного и того же пользователя всегда совместимы.

Две блокировки НЕ совместимы, если: установлены разными пользователями, имеют несовместимые виды (исключительная/разделяемая) и установлены на один и тот же ресурс.

Физическая реализация блокировок в СУБД

Физически блокировки представляют собой таблицу, которая находится в БД под названием master. Сама таблица блокировок носит имя syslockinfo.

Таблица условно имеет четыре поля:

  1. ИД блокирующей сессии SPID;
  2. что именно заблокировано RES ID;
  3. тип блокировки — S,U или X MODE (на самом деле в MS SQL их 22 типа, однако в связки с 1С используется только три);
  4. состояние блокировки — может принимать значение GRANT (установлена) и WAIT (ожидает своей очереди).

для него метод экземпляра destroy. Семантическое различие трудноуловимо, но становится понятным, когда задаются обратные вызовы before_destroy или возникают зависимые ассоциации, то есть дочерние объекты, которых нужно автоматически удалить вместе с родителем.

Блокировка базы данных

Термином блокировка обозначается техника, позволяющая предотвратить обновление одних и тех же записей несколькими одновременно работающими пользователями. При загрузке строк таблицы в модель ActiveRecord по умолчанию вообще не применяет блокировку. Если в некотором приложении Rails в любой момент времени обновлять данные может только один пользователь, то беспокоиться о блокировках не нужно.

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

конкурентностью . Спросите себя, какиеколлизии илигонки (race conditions) могут иметь место, если два пользователя попытаются обновить модель в один и тот же момент?

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

гие варианты, например блокирование таблиц целиком. У каждого подхода много сильных и слабых сторон, поэтому для максимально надежной работы приложения имеет смысл их комбинировать.

Оптимистическая блокировка

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

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

Если вы контролируете схему базы данных, то реализовать оптимистическую блокировку совсем просто. Достаточно добавить в таблицу целочисленную колонку с именем lock_version и значением по умолчанию 0:

Блокировка базы данных

AddLockVersionToTimesheets < ActiveRecord::Migration

add_column:timesheets, :lock_version, :integer, :default => 0

remove_column:timesheets, :lock_version end

Само наличие такой колонки изменяет поведение ActiveRecord. Если некоторая запись загружена в два экземпляра модели и сохранена с разными значениями атрибутов, то успешно завершится обновление первого экземпляра, а при обновлении второго будет возбуждено ис-

ключение ActiveRecord::StaleObjectError.

Для иллюстрации оптимистической блокировки напишем простой автономный тест:

class TimesheetTest < Test::Unit::TestCase

fixtures:timesheets, :users

def test_optimistic_locking_behavior first_instance = Timesheet.find(1) second_instance = Timesheet.find(1)

first_instance.approver = users(:approver) second_instance.approver = users(:approver2)

assert first_instance.save, "Успешно сохранен первый экземпляр"

assert_raises ActiveRecord::StaleObjectError do second_instance.save

Тест проходит, потому что при вызове save для второго экземпляра мы ожидаем исключения ActiveRecord::StaleObjectError. Отметим,

что метод save (без восклицательного знака) возвращает false и не возбуждает исключений, если сохранение не выполнено из-за ошибки контроля данных . Другие проблемы, например блокировка записи, могут приводить к исключению. Если вы хотите, чтобы колонка, содержащая номер версии, называлась неlock_version , а как-то иначе, измените эту настройку с помощью метода set_locking_column. Чтобы это изменение действовало глобально, добавьте в файлenvironment.rb такую строку:

ActiveRecord::Base.set_locking_column "alternate_lock_version"

Как и другие настройки ActiveRecord, эту можно задать на уровне модели, если включить в класс модели такое объявление:

class Timesheet < ActiveRecord::Base set_locking_column "alternate_lock_version"

Обработка исключения StaleObjectError

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

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

def update begin

@timesheet = Timesheet.find(params[:id]) @timesheet.update_attributes(params[:timesheet])

# куда-нибудь переадресовать rescue ActiveRecord::StaleObjectError

flash[:error] = "Табель был модифицирован, пока вы его редактировали." redirect_to:action => "edit", :id => @timesheet

У оптимистической блокировки есть ряд преимуществ. Для нее не нужны специальные механизмы СУБД, и реализуется она сравнительно просто. Как видно из примера, для обработки исключения Stale­ ObjectError потребовалось очень немного кода.

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

Пессимистическая блокировка

Для пессимистической блокировки требуется специальная поддержка со стороны СУБД (впрочем, в наиболее распространенных СУБД она есть). На время операции обновления блокируются некоторые строки в таблицах. Это не дает другим пользователям читать записи, которые будут обновлены, и тем самым предотвращает потенциальную возможность работы с устаревшими данными.

Одновременный конкурентный доступ может вызывать разные отрицательные эффекты, например чтение несуществующих данных или потерю модифицированных данных. Рассмотрим следующий практический пример, иллюстрирующий один из этих отрицательных эффектов, называемый грязным чтением. Пользователь U1 из отдела кадров получает извещение, что сотрудник "Василий Фролов" поменял место жительства. Он вносит соответствующее изменение в базу данных для данного сотрудника, но при просмотре другой информации об этом сотруднике он понимает, что изменил адрес не того человека. (В компании работают два сотрудника по имени Василий Фролов.) К счастью, приложение позволяет отменить это изменение одним нажатием кнопки. Он нажимает эту кнопку, уверенный в том, что данные после отмены операции изменения адреса уже не содержат никакой ошибки.

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

Чтобы предотвратить подобные проблемы в модели пессимистического одновременного конкурентного доступа, которую мы кратко описали в предыдущей статье, каждая система управления базами данных должна обладать механизмом для управления одновременным доступом к данным всеми пользователями. Для обеспечения согласованности данных в случае одновременного обращения к данным несколькими пользователями компонент Database Engine, подобно всем СУБД, применяет блокировки. Каждая прикладная программа блокирует требуемые ей данные, что гарантирует, что никакая другая программа не сможет модифицировать эти данные. Когда другая прикладная программа пытается получить доступ к заблокированным данным для их модификации, то система или завершает эту попытку ошибкой, или заставляет программу ожидать снятия блокировки.

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

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

Режимы блокировки

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

Разделяемая блокировка (shared lock)

Резервирует ресурс (страницу или строку) только для чтения. Другие процессы не могут изменять заблокированный таким образом ресурс, но, с другой стороны, несколько процессов могут одновременно накладывать разделяемую блокировку на один и тот же ресурс. Иными словами, чтение ресурса с разделяемой блокировкой могут одновременно выполнять несколько процессов.

Монопольная блокировка (exclusive lock)

Резервирует страницу или строку для монопольного использования одной транзакции. Блокировка этого типа применяется инструкциями DML (INSERT, UPDATE и DELETE), которые модифицируют ресурс. Монопольную блокировку нельзя установить, если на ресурс уже установлена разделяемая или монопольная блокировка другим процессом, т.е. на ресурс может быть установлена только одна монопольная блокировка. На ресурс (страницу или строку) с установленной монопольной блокировкой нельзя установить никакую другую блокировку.

Блокировка обновления (update lock)

Может быть установлена на ресурс только при отсутствии на нем другой блокировки обновления или монопольной блокировки. С другой стороны, этот тип блокировки можно устанавливать на объекты с установленной разделяемой блокировкой. В таком случае блокировка обновления накладывает на объект другую разделяемую блокировку. Если транзакция, которая модифицирует объект, подтверждается, и у объекта нет никаких других блокировок, блокировка обновления преобразовывается в монопольную блокировку. У объекта может быть только одна блокировка обновления.

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

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

Эта таблица интерпретируется следующим образом: предположим транзакция T1 имеет блокировку, указанную в заголовке соответствующей строки таблицы, а транзакция T2 запрашивает блокировку, указанную в соответствующем заголовке столбца таблицы. Значение "Да" в ячейке на пресечении строки и столбца означает, что транзакция T2 может иметь запрашиваемый тип блокировки, а значение "Нет", что не может.

Компонент Database Engine также поддерживает и другие типы блокировок, такие как кратковременные блокировки (latch lock) и взаимоблокировки (spin lock).

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

    разделяемая (shared, S);

    монопольная (exclusive, X);

    разделяемая с намерением (intent shared, IS);

    монопольная с намерением (intent exclusive, IX);

    разделяемая с монопольным намерением (shared with intent exclusive, SIX).

Разделяемые и монопольные типы блокировок для таблицы соответствуют одноименным блокировкам для строк и страниц. Обычно блокировка с намерением (intent lock) означает, что транзакция намеревается блокировать следующий нижележащий в иерархии объектов базы данных ресурс. Таким образом, блокировка с намерением помещается на уровне иерархии объектов, который выше того объекта, который этот процесс намеревается заблокировать. Это является действенным способом узнать, возможна ли подобная блокировка, а также устанавливается запрет другим процессам блокировать более высокий уровень, прежде чем процесс может установить требуемую ему блокировку.

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

Возможность совмещения разных типов блокировок
S X IS SIX IX
S Да Нет Да Нет Нет
X Нет Нет Нет Нет Нет
IS Да Нет Да Да Да
SIX Нет Нет Да Нет Нет
IX Нет Нет Да Нет Да

Гранулярность блокировки

Гранулярность блокировки определяет, какой ресурс блокируется в одной попытке блокировки. Компонент Database Engine может блокировать следующие ресурсы: строки, страницы, индексный ключ или диапазон индексных ключей, таблицы, экстент, саму базу данных. Система выбирает требуемую гранулярность блокировки автоматически.

Строка является наименьшим ресурсом, который можно заблокировать. Блокировка уровня строки также включает как строки данных, так и элементы индексов. Блокировка на уровне строки означает, что блокируется только строка, к которой обращается приложение. Поэтому все другие строки данной таблицы остаются свободными и их могут использовать другие приложения. Компонент Database Engine также может заблокировать страницу, на которой находится подлежащая блокировке строка.

В кластеризованных таблицах страницы данных хранятся на уровне узлов (кластеризованной) индексной структуры, и поэтому для них вместо блокировки строк применяется блокировка с индексными ключами.

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

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

Укрупнение блокировок

Если в процессе транзакции имеется большое количество блокировок одного уровня, то компонент Database Engine автоматически объединяет эти блокировки в одну уровня таблицы. Этот процесс преобразования большого числа блокировок уровня строки, страницы или индекса в одну блокировку уровня таблицы называется укрупнением блокировок (lock escalation) . Порогом укрупнения называется граница, на которой система баз данных применяет укрупнение блокировок. Пороги укрупнения устанавливаются динамически системой и не требуют настройки. (В настоящее время пороговым значением укрупнения блокировок является 5000 блокировок.)

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

Параметр table является значением по умолчанию и задает укрупнение блокировок на уровне таблиц. Параметр auto позволяет компоненту Database Engine самому выбирать уровень гранулярности, который соответствует схеме таблицы. Наконец, параметр disable отключает укрупнение блокировок в большинстве случаев. (В некоторых случаях компоненту Database Engine требуется наложить блокировку на уровне таблиц, чтобы предохранить целостность данных.)

В примере ниже показана отмена укрупнения блокировок для таблицы Employee:

USE SampleDb; ALTER TABLE Employee SET (LOCK_ESCALATION = DISABLE);

Настройка блокировок

Настройку блокировок можно осуществлять, используя подсказки блокировок (locking hints) или параметр LOCK_TIMEOUT инструкции SET. Эти возможности описываются в следующих разделах.

Подсказки блокировок (locking hints)

Подсказки блокировок задают тип блокировки, используемой компонентом Database Engine для блокировки табличных данных. Подсказки блокировки уровня таблиц применяются, когда требуется более точное управление типами блокировок, накладываемых на ресурс. (Подсказки блокировок перекрывают текущий уровень изоляции для сеанса.)

Все подсказки блокировок указываются в предложении FROM инструкции SELECT. Далее приводится список и краткое описание доступных подсказок блокировок:

UPDLOCK

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

TABLOCK

Устанавливается разделяемая (или монопольная) блокировка для таблицы. Все блокировки удерживаются до окончания транзакции.

ROWLOCK

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

PAGLOCK

Разделяемая блокировка таблицы заменяется разделяемой блокировкой страницы для каждой страницы, содержащей указанные строки.

NOLOCK

Синоним для READUNCOMMITTED, который мы рассмотрим при обсуждении уровней изоляции.

HOLDLOCK

Синоним для REPEATABLEREAD.

XLOCK

Устанавливается монопольная блокировка, удерживаемая до завершения транзакции. Если подсказка xlock указывается с подсказкой rowlock, paglock или tablock, монопольные блокировки устанавливаются на соответствующем уровне гранулярности.

READPAST

Указывает, что компонент Database Engine не должен считывать строки, заблокированные другими транзакциями.

Все эти параметры можно объединять вместе в любом имеющем смысл порядке. Например, комбинация подсказок TABLOCK с PAGLOCK не имеет смысла, поскольку каждая из них применяется для разных ресурсов.

Параметр LOCK_TIMEOUT

Чтобы процесс не ожидал освобождения блокируемого объекта до бесконечности, можно в инструкции SET использовать параметр LOCK_TIMEOUT. Этот параметр задает период в миллисекундах, в течение которого транзакция будет ожидать снятия блокировки с объекта. Например, если вы хотите чтобы период ожидания был равен восемь секунд, то это следует указать следующим образом:

SET LOCK_TIMEOUT 8000

Если данный ресурс не может быть предоставлен процессу в течение этого периода времени, инструкция завершается аварийно и выдается соответствующее сообщение об ошибке. Значение LOCK_TIMEOUT равное -1 (значение по умолчанию) указывает отсутствие периода ожидания, т.е. транзакция не будет ожидать освобождения ресурса совсем. (Подсказка блокировки READPAST предоставляет альтернативу параметру LOCK_TIMEOUT.)

Отображение информации о блокировках

Наиболее важным средством для отображения информации о блокировках является динамическое административное представление sys.dm_tran_locks . Это представление возвращает информацию о текущих активных ресурсах диспетчера блокировок. Каждая строка представления отображает активный в настоящий момент запрос на блокировку, которая была предоставлена или предоставление которой ожидается. Столбцы представления соответствуют двум группам: ресурсам и запросам. Группа ресурсов описывает ресурсы, на блокировку которых делается запрос, а группа запросов описывает запрос блокировки. Наиболее важными столбцами этого представления являются следующие:

    resource_type - указывает тип ресурса;

    resource_database_id - задает идентификатор базы данных, к которой принадлежит данный ресурс;

    request_mode - задает режим запроса;

    request_status - задает текущее состояние запроса.

В примере ниже показан запрос, использующий представление sys.dm_tran_locks для отображения блокировок в состоянии ожидания:

Взаимоблокировки

Взаимоблокировка (deadlock) - это особая проблема одновременного конкурентного доступа, в которой две транзакции блокируют друг друга. В частности, первая транзакция блокирует объект базы данных, доступ к которому хочет получить другая транзакция, и наоборот. (В общем, взаимоблокировка может быть вызвана несколькими транзакциями, которые создают цикл зависимостей.) В примере ниже показана взаимоблокировка двумя транзакциями. (При использовании базы данных небольшого размера, одновременный конкурентный доступ процессов нельзя получить естественным образом, вследствие очень быстрого выполнения каждой транзакции. Поэтому в примере ниже используется инструкция WAITFOR, чтобы приостановить обе транзакции на десять секунд, чтобы эмулировать взаимоблокировку.)

USE SampleDb; BEGIN TRANSACTION BEGIN TRANSACTION UPDATE Works_on UPDATE Employee SET Job = "Менеджер" SET DepartamentNumber = "d2" WHERE EmpId = 25348 WHERE Id = 28559 AND ProjectNumber = "p2" WAITFOR DELAY "00:00:10" WAITFOR DELAY "00:00:10" UPDATE Employee DELETE FROM Works_on SET LastName = "Фролова" WHERE EmpId = 25348 WHERE LastName = "Вершинина" AND ProjectNumber = "p2" COMMIT COMMIT

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

Сообщение 1205, уровень 13, состояние 45 Транзакция (процесс с идентификатором 56) находится во взаимной блокировке с другим процессом и выбрана в качестве потерпевшей взаимоблокировки. Повторите выполнение команды.)

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

Вы можете повлиять на то, какая транзакция будет выбрана системой в качестве "жертвы" взаимоблокировки, присвоив в инструкции SET параметру DEADLOCK_PRIORITY один из 21 (от -10 до 10) разных уровней приоритета взаимоблокировки. Константа LOW соответствует значению -5, NORMAL (значение по умолчанию) - значению 0, а константа HIGH - значению 5. Сеанс "жертва" выбирается в соответствии с приоритетом взаимоблокировки сеанса.