Ответ 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" ), потому что будет очень сложно точно предсказать, какие транзакции могут способствовать зависимостей чтения/записи и их необходимо отменить, чтобы предотвратить аномалии сериализации.