2016-10-17 1 views
1

У нас есть приложение с простой таблицейPostgres 9.4 обнаруживает Тупик, когда чтение-модификация-запись на одной таблице

given_entity{ 
    UUID id; 
    TimeStamp due_time; 
    TimeStamp process_time; 
} 

Это приложение весной загрузки (1.2.5.RELEASE), который использует весенне-данных- jpa.1.2.5.RELEASE с гибернацией 4.3.10.FINAL as jpa provier.
У нас есть 5 экземпляров этого приложения, каждый из которых имеет планировщик, работающий каждые две секунды и запрашивающий базу данных для строк, которые до сих пор не имеют обработанных должностей в течение последних 2 минут;

SELECT * FROM given_entity 
WHERE process_time is null and due_time between now() and NOW() - INTERVAL '2 minutes' 
FOR UPDATE 

Требование: каждая строка таблицы выше успешно обрабатывается одним из экземпляров приложения. Затем экземпляр приложения обрабатывает эти строки и обновляет его поле process_time в одной транзакции. Это может занять не более 2 секунд, что является интервалом планировщика. Также у нас нет индекса, кроме индекса PK на этой таблице. Следует отметить, что эти экземпляры могут вставлять строки этой таблицы, которые вызывается отдельно клиентами.

Проблема: в журналах я вижу это сообщение от PostgreSQL (редко, но бывает)

ERROR: deadlock detected 
Detail: Process 10625 waits for ShareLock on transaction 25382449; blocked by process 10012. 
Process 10012 waits for ShareLock on transaction 25382448; blocked by process 12238. 
Process 12238 waits for AccessExclusiveLock on tuple (1371,45) of relation 19118 of database 19113; blocked by process 10625. 
Hint: See server log for query details. 
Where: while locking tuple (1371,45) in relation "given_entity" 

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

+0

Вы можете вставить код, в котором вы обновляете таблицу? ** select for update ** блокирует изменения другими транзакциями, нужно быть осторожным с ним. –

+0

, чтобы избежать этих проблем в целом, вы можете использовать оптимизирующий механизм блокировки Hibernate (если только не существует действительно реальной причины блокировки записей, которые вы хотите обновить tio). Возможно, это также может быть полезно: http://blog.2ndquadrant.com/postgresql-anti-patterns-read-modify-write-cycles/ –

+0

Для обновления мы просто обновляем поле process_time. Что-то вроде «UPDATE given_entity set process_time = now(), где id =? ' – Abareghi

ответ

1

Это может случиться.

Есть, вероятно, несколько строк, которые удовлетворяют условию

due_time BETWEEN now() AND now() - INTERVAL '2 minutes' 

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

Возможно, даже тупик происходит между двумя вашими заявлениями SELECT ... FOR UPDATE.
Поскольку вы говорите, что в таблице нет индекса первичного ключа, запрос должен выполнить последовательное сканирование. В PostgreSQL нет фиксированного порядка для строк, возвращаемых из последовательного сканирования. Скорее, если одновременно выполняются два последовательных сканирования, второй будет “ piggy-back ” на первом и начнет сканирование таблицы в текущем местоположении первого последовательного сканирования.

Вы можете проверить это, установив параметр synchronize_seqscans на номер off и посмотреть, исчезли ли тупики. Другой вариант - сделать блокировку SHARE ROW EXCLUSIVE на столе перед запуском оператора.

+0

Большое спасибо. Я также с подозрением относился к другому порядку отсканированных строк. Но так как я не смог воспроизвести его в модульном тесте с одновременным написанием 100 потоков и 100 потоков одновременно, я не был уверен, что это так. Еще одна вещь, которую я не мог объяснить, - это то, почему здесь получается AccessExclusiveLock? Это потому, что в то же время произойдет вставка, и postgresql хочет переиндексировать PK? Я не уверен, как «synchronize_sequence = off» повлияет на нашу производительность. Считаете ли вы, что индекс в столбцах запроса поиска может смягчить это? – Abareghi

+0

Что касается «SHARE ROW EXCLUSIVE», я бы предпочел не пойти на явный замок, если у меня есть другой выбор. – Abareghi

+0

И что еще более важно, «SHARE ROW EXCLUSIVE» противоречит другим замкам, чем «ROW SHARE», который приобретается с помощью «SELECT FOR UPDATE». Я не уверен, как это влияет на производительность. – Abareghi

2

Процесс A пытается заблокировать строку 1, за которой следует строка 2. Между тем, процесс B пытается заблокировать строку 2, а затем строку 1. Это все, что требуется для запуска тупика.

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

SELECT * FROM given_entity 
WHERE process_time is null and due_time between now() and NOW() - INTERVAL '2 minutes' 
ORDER BY id 
FOR UPDATE 

В Postgres 9.5+, вы можете просто игнорировать любую строку, который заблокирован другим процессом с помощью FOR UPDATE SKIP LOCKED.

+0

Большое спасибо. Поэтому, если я правильно ее понимаю, postgresql найдет результат «SELECT ...», а затем закроет строки результатов. Именно поэтому ваше предлагаемое решение позволит избежать проблемы? – Abareghi

+0

Правильно, блокировка выполняется после сортировки: https://www.postgresql.org/message-id/2382.1170171581%40sss.pgh.pa.us –

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