Блокировка предикатов в PostgreSQL 9.2.1 с изоляцией Serializable - программирование
Подтвердить что ты не робот

Блокировка предикатов в PostgreSQL 9.2.1 с изоляцией Serializable

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

Надеюсь, кто-нибудь может просветить меня: -)

В соответствии с документацией: блокировки Predicate в PostgreSQL, как и в большинстве других систем баз данных, основаны на данных, фактически доступных транзакцией

Это звучит хорошо, тогда почему происходит следующее?

CREATE TABLE mycustomer(cid integer PRIMARY KEY, licenses integer);
CREATE TABLE mydevice(id integer PRIMARY KEY, cid integer REFERENCES 
mycustomer (cid), status varchar(10));

INSERT INTO mycustomer(cid, licenses) VALUES (1, 5);
INSERT INTO mycustomer(cid, licenses) VALUES (2, 5);

    Request 1                            Request2
BEGIN TRANSACTION ISOLATION 
LEVEL SERIALIZABLE;
                                         BEGIN TRANSACTION ISOLATION 
                                         LEVEL SERIALIZABLE;
SELECT * from mydevice where cid = 1;

                                         SELECT * from mydevice where cid = 2;
INSERT INTO mydevice(id, cid, status) 
VALUES (1, 1, 'ok');

                                         INSERT INTO mydevice(id, cid, status)         
                                         VALUES (2, 2, 'ok');
commit;
(=ok)                                 
                                         commit;
                                         (=rollback)

Я понимаю, что вставки из запроса 1 и запроса 2 не противоречат предыдущим чтениям и, следовательно, не должно быть никаких ошибок. Почему я получаю сообщение "ОШИБКА: невозможно выполнить сериализацию доступа из-за зависимостей чтения/записи между транзакциями".

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

Эти операции выполняются из приложения Java. В принципе я думаю о создании таблицы блокировки для удовлетворения моих потребностей. Любые идеи?

Большое спасибо!

4b9b3361

Ответ 1

На странице "Изоляция транзакций" :

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

...

  • Последовательное сканирование всегда требует блокировки предикатов на уровне отношений. Это может привести к увеличению скорости сбоев сериализации.

An EXPLAIN на этом SELECT может сообщить вам, что делает план запроса, но если таблица небольшая (или пустая!), PostgreSQL почти наверняка выберет последовательное сканирование вместо ссылки на индекс. Это приведет к блокировке предикатов во всей таблице, что приведет к сбою сериализации всякий раз, когда другая транзакция что-либо сделает с таблицей.

В моей системе:

isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                        QUERY PLAN                        
----------------------------------------------------------
 Seq Scan on mydevice  (cost=0.00..23.38 rows=5 width=46)
   Filter: (cid = 1)
(2 rows)

Вы можете попробовать добавить индекс и заставить его использовать это:

isolation=# CREATE INDEX mydevice_cid_key ON mydevice (cid);
CREATE INDEX
isolation=# SET enable_seqscan = off;
SET
isolation=# EXPLAIN SELECT * from mydevice where cid = 1;
                                    QUERY PLAN                                    
----------------------------------------------------------------------------------
 Index Scan using mydevice_cid_key on mydevice  (cost=0.00..8.27 rows=1 width=46)
   Index Cond: (cid = 1)
(2 rows)

Однако это не правильное решение. Вернемся немного назад.

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

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

Сообщение об ошибке:

ERROR:  could not serialize access due to read/write dependencies among transactions
DETAIL:  Reason code: Canceled on identification as a pivot, during commit attempt.
HINT:  The transaction might succeed if retried.

Этот ключ является ключевым. Вашему приложению необходимо уловить ошибки в сериализации и повторить всю операцию. Это верно, когда SERIALIZABLE находится в игре - он гарантирует правильность последовательности, несмотря на concurrency, но это не может сделать это без помощи вашего приложения. Иными словами, если вы на самом деле выполняете параллельные изменения, единственный способ, которым PostgreSQL может удовлетворить требования к изоляции, - это попросить ваше приложение сериализоваться. Таким образом:

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

Ответ 2

Для более любопытных, в Postgres 9.1 Исходный код, если вы посмотрите src/backend/storage/lmgr/README-SSI, есть много подробного описания блокировки Predicate и транзакций Serializable.

Вот фрагмент того же:

Сериализуемая изоляция моментальных снимков (SSI) и блокировка предикатов    ================================================== =========

Этот код находится в каталоге lmgr, так как около 90% его   реализация блокировки предикатов, которая требуется для SSI,   а не напрямую связан с самим SSI. Когда другое использование   для блокировки предикатов оправдывает усилие дразнить эти две вещи  Кроме того, этот файл README должен быть разделен.

Credits:

Эта функция была разработана Кевином Гритнером и Дэн Р. К. Портом,  с обзором и предложениями от Джо Конвея, Хейкки Линнакангаса и  Джефф Дэвис. Он основан на работе, опубликованной в этих статьях:

 Michael J. Cahill, Uwe Röhm, and Alan D. Fekete. 2008.
 Serializable isolation for snapshot databases.
 In SIGMOD '08: Proceedings of the 2008 ACM SIGMOD
 international conference on Management of data,
 pages 729-738, New York, NY, USA. ACM.
 http://doi.acm.org/10.1145/1376616.1376690

 Michael James Cahill. 2009.
 Serializable Isolation for Snapshot Databases.
 Sydney Digital Theses.
 University of Sydney, School of Information Technologies.
 http://hdl.handle.net/2123/5353