13.3. Явная блокировка#

13.3. Явная блокировка

13.3. Явная блокировка

Tantor SE предоставляет различные режимы блокировки для контроля параллельного доступа к данным в таблицах. Эти режимы могут использоваться для блокировки, контролируемой приложением, в ситуациях, когда MVCC не дает желаемого поведения. Кроме того, большинство команд Tantor SE автоматически получают блокировки соответствующих режимов, чтобы гарантировать, что ссылочные таблицы не будут удалены или изменены несовместимым образом во время выполнения команды. (Например, TRUNCATE не может быть безопасно выполнена одновременно с другими операциями на той же таблице, поэтому она получает блокировку ACCESS EXCLUSIVE на таблицу для обеспечения этого).

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

13.3.1. Таблица-уровень блокировок

Список ниже показывает доступные режимы блокировки и контексты, в которых они автоматически используются Tantor SE. Вы также можете явно получить любую из этих блокировок с помощью команды LOCK. Помните, что все эти режимы блокировки являются блокировками на уровне таблицы, даже если в названии есть слово row; названия режимов блокировки исторические. В некоторой степени названия отражают типичное использование каждого режима блокировки, но семантика одинакова. Единственное реальное отличие между режимами блокировки - это набор режимов блокировки, с которыми каждый конфликтует (см. Таблица 13.2). Две транзакции не могут удерживать блокировки конфликтующих режимов на одной и той же таблице одновременно. (Однако транзакция никогда не конфликтует сама с собой. Например, она может получить блокировку ACCESS EXCLUSIVE и позже получить блокировку ACCESS SHARE на той же таблице). Неконфликтующие режимы блокировки могут быть удерживаемыми одновременно множеством транзакций. Обратите внимание, что некоторые режимы блокировки конфликтуют сами с собой (например, блокировка ACCESS EXCLUSIVE не может быть удерживаемой более чем одной транзакцией одновременно), в то время как другие не конфликтуют сами с собой (например, блокировка ACCESS SHARE может быть удерживаемой несколькими транзакциями).

Режимы блокировки на уровне таблицы

ACCESS SHARE (AccessShareLock)

Конфликты с режимом блокировки ACCESS EXCLUSIVE только.

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

ROW SHARE (RowShareLock)

Конфликтует с режимами блокировки EXCLUSIVE и ACCESS EXCLUSIVE.

Команда SELECT получает блокировку этого режима на всех таблицах, для которых указано одно из следующих FOR UPDATE, FOR NO KEY UPDATE, FOR SHARE или FOR KEY SHARE (в дополнение к блокировкам ACCESS SHARE на любых других таблицах, на которые есть ссылки без явного указания опции блокировки FOR ...).

ROW EXCLUSIVE (RowExclusiveLock)

Конфликтует с режимами блокировки SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE.

Команды UPDATE, DELETE, INSERT и MERGE получают этот режим блокировки на целевой таблице (в дополнение к блокировкам ACCESS SHARE на любые другие ссылочные таблицы). В общем случае этот режим блокировки будет получен любой командой, которая изменяет данные в таблице.

SHARE UPDATE EXCLUSIVE (ShareUpdateExclusiveLock)

Конфликтует с режимами блокировки SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от одновременных изменений схемы и запусков VACUUM.

Приобретено с помощью VACUUM (без FULL), ANALYZE, CREATE INDEX CONCURRENTLY, CREATE STATISTICS, COMMENT ON, REINDEX CONCURRENTLY, и определенные варианты ALTER INDEX и ALTER TABLE (для полной информации см. документацию по этим командам).

SHARE (ShareLock)

Конфликты с режимами блокировки ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от одновременных изменений данных.

Приобретено с помощью CREATE INDEX (без CONCURRENTLY).

SHARE ROW EXCLUSIVE (ShareRowExclusiveLock)

Конфликты с режимами блокировки ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим защищает таблицу от одновременных изменений данных и является самоисключающим, поэтому только одна сессия может его удерживать в данный момент.

Взято с помощью команды CREATE TRIGGER и некоторых форм ALTER TABLE.

EXCLUSIVE (ExclusiveLock)

Конфликты с режимами блокировки ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE. Этот режим позволяет только одновременные блокировки ACCESS SHARE, то есть только чтение из таблицы может выполняться параллельно с транзакцией, удерживающей этот режим блокировки.

Приобретено с помощью REFRESH MATERIALIZED VIEW CONCURRENTLY.

ACCESS EXCLUSIVE (AccessExclusiveLock)

Соответствует блокировкам всех режимов (ACCESS SHARE, ROW SHARE, ROW EXCLUSIVE, SHARE UPDATE EXCLUSIVE, SHARE, SHARE ROW EXCLUSIVE, EXCLUSIVE и ACCESS EXCLUSIVE). Этот режим гарантирует, что держатель является единственной транзакцией, которая имеет доступ к таблице любым способом.

Приобретается с помощью команд DROP TABLE, TRUNCATE, REINDEX, CLUSTER, VACUUM FULL, и REFRESH MATERIALIZED VIEW (без CONCURRENTLY) . Многие формы команд ALTER INDEX и ALTER TABLE также приобретают блокировку на этом уровне. Это также является режимом блокировки по умолчанию для операторов LOCK TABLE, которые не указывают явно режим.

Подсказка

Только блокировка ACCESS EXCLUSIVE препятствует выполнению оператора SELECT (без FOR UPDATE/SHARE).

После получения блокировки она обычно удерживается до конца транзакции. Но если блокировка получена после установки точки сохранения, то блокировка освобождается немедленно, если точка сохранения откатывается. Это соответствует принципу, что ROLLBACK отменяет все эффекты команд, выполненных после точки сохранения. То же самое относится к блокировкам, полученным внутри блока исключения PL/pgSQL: при выходе из блока из-за ошибки освобождаются блокировки, полученные внутри него.

Таблица 13.2. Конфликтующие режимы блокировки

Запрошенный режим блокировкиСуществующий режим блокировки
ACCESS SHAREROW SHAREROW EXCL.SHARE UPDATE EXCL.SHARESHARE ROW EXCL.EXCL.ACCESS EXCL.
ACCESS SHARE       X
ROW SHARE      XX
ROW EXCL.    XXXX
SHARE UPDATE EXCL.   XXXXX
SHARE  XX XXX
SHARE ROW EXCL.  XXXXXX
EXCL. XXXXXXX
ACCESS EXCL.XXXXXXXX

13.3.2. Блокировки на уровне строк

В дополнение к блокировкам на уровне таблицы, существуют блокировки на уровне строк, которые автоматически используются Tantor SE в следующих контекстах. См. Таблица 13.3 для полной таблицы конфликтов блокировок на уровне строк. Обратите внимание, что транзакция может удерживать конфликтующие блокировки на одной и той же строке, даже в разных подтранзакциях; но помимо этого, две транзакции никогда не могут удерживать конфликтующие блокировки на одной и той же строке. Блокировки на уровне строк не влияют на запросы данных; они блокируют только писателей и блокировщиков для одной и той же строки. Блокировки на уровне строк освобождаются при завершении транзакции или во время отката точки сохранения, так же как и блокировки на уровне таблицы.

Режимы блокировки на уровне строк

FOR UPDATE

FOR UPDATE вызывает блокировку строк, полученных оператором SELECT, как при выполнении операции обновления. Это предотвращает их блокировку, изменение или удаление другими транзакциями до завершения текущей транзакции. То есть, другие транзакции, которые пытаются выполнить операции UPDATE, DELETE, SELECT FOR UPDATE, SELECT FOR NO KEY UPDATE, SELECT FOR SHARE или SELECT FOR KEY SHARE для этих строк будут заблокированы до завершения текущей транзакции; соответственно, SELECT FOR UPDATE будет ожидать одновременную транзакцию, которая выполнила любую из этих команд для той же строки, а затем заблокирует и вернет обновленную строку (или не вернет строку, если она была удалена). Однако, в рамках транзакции с уровнем REPEATABLE READ или SERIALIZABLE будет сгенерирована ошибка, если строка, которую необходимо заблокировать, изменилась с момента начала транзакции. Для дальнейшего обсуждения см. Раздел 13.4.

Режим блокировки FOR UPDATE также устанавливается при выполнении команды DELETE для строки, а также при выполнении команды UPDATE, которая изменяет значения определенных столбцов. В настоящее время для команды UPDATE рассматриваются только те столбцы, которые имеют уникальный индекс, который может быть использован во внешнем ключе (при этом не рассматриваются частичные индексы и индексы на выражениях), но это может измениться в будущем.

FOR NO KEY UPDATE

Поведение аналогично FOR UPDATE, за исключением того, что получаемая блокировка является слабее: эта блокировка не будет блокировать команды SELECT FOR KEY SHARE, которые пытаются получить блокировку на те же строки. Этот режим блокировки также получается любым UPDATE, который не получает блокировку FOR UPDATE.

FOR SHARE

Ведет себя аналогично FOR NO KEY UPDATE, за исключением того, что он получает общую блокировку, а не исключительную блокировку для каждой извлеченной строки. Общая блокировка блокирует другие транзакции от выполнения UPDATE, DELETE, SELECT FOR UPDATE или SELECT FOR NO KEY UPDATE для этих строк, но не мешает им выполнять SELECT FOR SHARE или SELECT FOR KEY SHARE.

FOR KEY SHARE

Ведет себя аналогично FOR SHARE, за исключением того, что блокировка слабее: SELECT FOR UPDATE блокируется, но не SELECT FOR NO KEY UPDATE. Ключевая общая блокировка блокирует другие транзакции от выполнения DELETE или любого UPDATE, который изменяет значения ключей, но не другие UPDATE, и также не предотвращает SELECT FOR NO KEY UPDATE, SELECT FOR SHARE или SELECT FOR KEY SHARE.

Tantor SE не сохраняет никакую информацию о измененных строках в памяти, поэтому нет ограничений на количество заблокированных строк одновременно. Однако блокировка строки может вызвать запись на диск, например, SELECT FOR UPDATE изменяет выбранные строки для их пометки как заблокированные, и поэтому приводит к записи на диск.

Таблица 13.3. Конфликтующие блокировки на уровне строк

Запрошенный режим блокировкиТекущий режим блокировки
ДЛЯ КЛЮЧА СО СОВМЕСТНЫМ ДОСТУПОМДЛЯ СОВМЕСТНОГО ДОСТУПАДЛЯ ОБНОВЛЕНИЯ БЕЗ БЛОКИРОВКИ КЛЮЧАДЛЯ ОБНОВЛЕНИЯ
ДЛЯ КЛЮЧА СО СТАТУСОМ "SHARE"   X
ДЛЯ ЧТЕНИЯ  XX
ДЛЯ ОБНОВЛЕНИЯ БЕЗ КЛЮЧА XXX
ДЛЯ ОБНОВЛЕНИЯXXXX

13.3.3. Блокировки на уровне страницы

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

13.3.4. Взаимные блокировки

Использование явной блокировки может увеличить вероятность возникновения взаимоблокировок, когда две (или более) транзакции удерживают блокировки, которые другая транзакция хочет получить. Например, если транзакция 1 получает исключающую блокировку на таблицу A, а затем пытается получить исключающую блокировку на таблицу B, в то время как транзакция 2 уже удерживает исключающую блокировку на таблицу B и теперь хочет получить исключающую блокировку на таблицу A, то ни одна из них не может продолжить выполнение. Tantor SE автоматически обнаруживает ситуации взаимоблокировки и разрешает их, прерывая одну из вовлеченных транзакций и позволяя другим завершиться. (Точно определить, какая транзакция будет прервана, сложно предсказать и на это нельзя полагаться).

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

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 11111;

Это приобретает блокировку на уровне строки для строки с указанным номером счета. Затем вторая транзакция выполняется:

UPDATE accounts SET balance = balance + 100.00 WHERE acctnum = 22222;
UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 11111;

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

UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;

Транзакция один пытается получить блокировку на уровне строки для указанной строки, но не может: транзакция два уже удерживает такую блокировку. Поэтому она ожидает завершения транзакции два. Таким образом, транзакция один блокируется на транзакции два, а транзакция два блокируется на транзакции один: возникает ситуация взаимной блокировки. Tantor SE обнаружит эту ситуацию и прервет одну из транзакций.

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

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

13.3.5. Рекомендательные блокировки

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

Есть два способа получить рекомендательную блокировку в Tantor SE: на уровне сессии или на уровне транзакции. Полученная на уровне сессии рекомендательная блокировка сохраняется до явного освобождения или завершения сессии. В отличие от стандартных запросов на блокировку, запросы на рекомендательную блокировку на уровне сессии не учитывают семантику транзакции: блокировка, полученная во время транзакции, которая позже откатывается, все равно будет сохранена после отката, и, аналогично, разблокировка будет действительна даже если вызывающая транзакция завершится с ошибкой. Блокировка может быть получена несколько раз владеющим процессом; для каждого завершенного запроса на блокировку должен быть соответствующий запрос на разблокировку, прежде чем блокировка будет фактически освобождена. Запросы на блокировку на уровне транзакции, с другой стороны, ведут себя более похоже на обычные запросы на блокировку: они автоматически освобождаются в конце транзакции, и нет явной операции разблокировки. Такое поведение часто более удобно, чем поведение на уровне сессии, для краткосрочного использования рекомендательной блокировки. Запросы на блокировку на уровне сессии и на уровне транзакции для одного и того же идентификатора рекомендательной блокировки будут блокировать друг друга ожидаемым образом. Если сессия уже удерживает определенную рекомендательную блокировку, дополнительные запросы от нее всегда будут успешными, даже если другие сессии ожидают блокировки; это утверждение верно независимо от того, является ли существующая блокировка на уровне сессии или на уровне транзакции.

Как и все блокировки в Tantor SE, полный список рекомендательных блокировок, в настоящее время удерживаемых любой сессией, можно найти в системном представлении pg_locks.

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

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

SELECT pg_advisory_lock(id) FROM foo WHERE id = 12345; -- ok
SELECT pg_advisory_lock(id) FROM foo WHERE id > 12345 LIMIT 100; -- danger!
SELECT pg_advisory_lock(q.id) FROM
(
  SELECT id FROM foo WHERE id > 12345 LIMIT 100
) q; -- ok

В приведенных выше запросах вторая форма опасна, потому что LIMIT не гарантируется применяться перед выполнением функции блокировки. Это может привести к получению некоторых блокировок, которые приложение не ожидало, и, следовательно, не смогло бы их освободить (пока не завершится сессия). С точки зрения приложения такие блокировки будут висячими, хотя они все еще будут видны в pg_locks.

Все функции, предоставляемые для работы с рекомендательными блокировками, описаны в Раздел 9.27.10.