2013-08-30 2 views
4

Я хотел бы лучше понять механизм блокировки в postgres.Тупик с участием ограничения внешнего ключа

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

Почему это так?

p.s. Пожалуйста, не предлагайте удалять «выбрать для обновления».

Сценарий

Transaction 1  Transaction 2 
BEGIN    . 
update apple;  . 
.     BEGIN 
.     select tree for update; 
.     update apple; 
.     --halts because of the other transaction locking an apple 
update apple;  . 
-- deadlock  . 
        COMMIT 
        --transaction succeeds 

Код

Если вы хотите попробовать это в вашем Postgres - вот код, который вы можете копировать/вставить.

У меня есть следующая DB Schema

CREATE TABLE trees (
    id  integer primary key 
); 

create table apples (
    id  integer primary key, 
    tree_id integer references trees(id) 
); 

и очень простые данные

insert into trees values(1); 
insert into apples values(1,1); 

Есть два простых сделок. Один из них обновляет яблоки, второй - блокирует дерево и обновляет яблоко.

BEGIN; 
    UPDATE apples SET id = id WHERE id = 1; 
    -- run second transaction in paralell 
    UPDATE apples SET id = id WHERE id = 1; 
COMMIT; 

BEGIN; 
    SELECT id FROM trees WHERE id = 1 FOR UPDATE; 
    UPDATE apples SET id = id WHERE id = 1; 
COMMIT; 

Когда я запускаю их - взаимоблокировка происходит во втором обновлении первой транзакции.

ERROR: deadlock detected 
DETAIL: Process 81122 waits for ShareLock on transaction 227154; blocked by process 81100. 
Process 81100 waits for ShareLock on transaction 227153; blocked by process 81122. 
CONTEXT: SQL statement "SELECT 1 FROM ONLY "public"."trees" x WHERE "id" OPERATOR(pg_catalog.=) $1 FOR SHARE OF x" 
+0

Какая версия это? Это не похоже на то, что 'ShareLock' используется в последней версии. Требуется ли 'UPDATE' во второй транзакции? –

ответ

8

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

В частности, ваше select tree for update заявление приобретает исключительную блокировку на деревьях. А операторы update apples получают эксклюзивную блокировку для соответствующих яблок.

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

create constraint trigger ... on ... from ... 

http://www.postgresql.org/docs/current/static/sql-createtrigger.html

во всяком случае, эти триггеры будут работать то, что сводится к следующему:

select exists (select 1 from trees where id = 1); 

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

В результате Postgres затворяет тупик.

+2

Я считаю, что это правильный ответ. В транзакции 2 решение будет состоять в том, чтобы сделать это '' для блокировки '' для блокировки в строке деревьев, предоставляя неэксклюзивную блокировку, которая гарантирует, что никто не удалит или не обновит эту строку: '' SELECT id FROM trees WHERE id = 1 FOR SHARE ''. Если вы используете Postgres 9.3, вы можете использовать версию '' FOR KEY SHARE', и любые обновления на деревьях должны использовать '' FOR NO KEY UPDATE'', если они не изменят FK - более параллельные. См. Http://michael.otacoo.com/postgresql-2/postgres-9-3-feature-highlight-for-key-share-and-for-no-key-update/ – RichVel

+2

@RichVel Это, с добавленной стоимостью вашего комментария, должен быть принятым ответом. –

-3

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

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

Сделка 1 делает первые UPDATE. Это получает блокировку уровня строки в строке в apples. Во время операции он также получает блокировку по индексу в trees. Транзакция еще не совершена, поэтому блокировка данных на уровне строк по-прежнему удерживается посредством передачи 1. Однако блокировка индекса на trees немедленно освобождается. Не знаю, почему Postgres делает это для всех типов индексов.

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

Сделка 1 возвращается и снова пытается вернуть UPDATE. Замок на apples в порядке, так как он уже есть. Блокировка на trees, тем не менее, блокируется, поскольку транзакция 2 уже есть.

Добавление UPDATE в транзакции 2 заставляет его ждать на транзакции 1, вызывая тупик.

EDIT:

Я вернулся, чтобы исследовать это еще немного теперь, когда у меня есть Postgres установлен. На самом деле это действительно странно. Я посмотрел на pg_locks после совершения сделки 2.

сделка 1 имеет следующие замки:

  • RowExclusive на apples_pkey и яблоки
  • Exclusive на его TransactionID и virtualxid

Сделка 2 имеет следующие блокировки (и многие другие нерелевантные):

  • AccessShare на trees_pkey
  • RowShare на деревьях
  • Exclusive на его TransactionID и virtualxid
  • RowExclusive на apples_pkey и яблоки
  • Exclusive на кортеж яблок

Сделка 2 также ожидает получения Share lock на транзакции 1.

Интересно, что two transactions can hold a RowExclusive lock on the same table. Однако исключающие блокировки конфликтуют с Share, поэтому Transaction 2 ожидает транзакционного идентификатора транзакции 1. docs упоминает транзакционные блокировки как способ дождаться другой транзакции. Следовательно, похоже, что транзакция 2, хотя и совершена, все еще ждет транзакции 1.

Когда транзакция 1 продолжается, она хочет приобрести блокировку Share на транзакции 2, и это создает тупик. Почему он хочет приобрести блокировку акций на транзакции 2? Не слишком уверен в этом. The docs Подскажите, что эта информация недоступна в pg_locks. Я собираюсь предположить, что это связано с MVCC, но для меня это все еще загадка.

+1

Это связано с тем, что внешние замки ключей не являются блокировками индекса, см. Ответ Denis, который является правильным. – RichVel

+0

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

+1

Будучи действительно смущенным, больше интересуется вопросами, чем отвечать на них. [Также, Postgre явно неверно для Postgres.] (Http://wiki.postgresql.org/wiki/Identity_Guidelines) –

Смежные вопросы