2014-01-30 3 views
10

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

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

Я использую следующую идиому:

if [user does not exist] then [create user] 

Код [user does not exist] является:

SELECT id FROM myschema.users WHERE userId='xyz' 

и я проверить, возвращается ли какая-либо строка.

The (упрощенный) код [create user] является:

INSERT INTO myschema.users VALUES ('xyz') 

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

Key (id)=(xyz) already exists 

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

Согласно документации PostgreSQL по умолчанию, когда я неявно запускаю транзакцию, таблица блокируется до тех пор, пока я ее не фиксирую. Я не использую autocommit, и я совершаю транзакцию только в блоках, например. после всего if-else блок.

Действительно, я мог бы поместить в SQL-файл if-else, но это не решает мою проблему блокировки вообще. Я предполагал, что парадигма «победит все это» будет работать, и что первый работник, которому удается выполнить команду SELECT, будет владеть замками, пока не назовет COMMIT.

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

+0

Я уверен, что это неправильное решение, но для нас работал следующий подход: 'User.transaction {User.update_all ({userId: user_id}, {userId: user_id}); User.create! (UserId: 'xyz'), если User.exists? (UserId: 'xyz')} '. Первая «фальшивая» команда обновления блокирует строку (если она существует), следующая создает новую строку (если она не существует). Мы также устанавливаем определенный уровень изоляции транзакций, насколько я помню, и мы использовали mysql, а не postgresql. Это все, что я помню. – DNNX

+0

Взгляните на http://stackoverflow.com/questions/17267417/how-do-i-do-an-upsert-merge-insert-on-duplicate-update-in-postgresql и его ссылки. –

ответ

13

Вы должны заботиться об уровне изоляции транзакций. Он должен быть установлен на «SERIALIZABLE».

Причина: Phantom Reads - Транзакция не блокирует всю таблицу, а только строки, которые уже были прочитаны транзакцией.

Итак, если другая транзакция вставляет новые данные, они еще не заблокированы, и появляется ошибка.

Serializable избегает этого, блокируя все другие транзакции, пока этот не закончен.

Вы можете сделать это с помощью

SET TRANSACTION ISOLATION LEVEL SERIALIZABLE; 

The документаций: http://www.postgresql.org/docs/9.1/static/transaction-iso.html

Если вы хотите узнать больше об этой теме, я настоятельно рекомендую вам это видео: http://www.youtube.com/watch?v=zz-Xbqp0g0A

+1

Хотя изоляция транзакций «SERIALIZABLE» - это чистое решение, оно также довольно дорого *. И вам нужно быть готовым к сбоям сериализации и повторить попытку в этом случае. Здесь более дешевая (а также чистая) альтернатива для проблемы «INSERT или SELECT»: http://stackoverflow.com/questions/15939902/is-select-or-insert-in-a-function-prone-to-race -условиям/15950324 # 15950324 –

7

На самом деле, после того, как некоторые возились с ISOLATION LEVEL SERIALIZABLE, как было предложено @maja, я обнаружил гораздо более простой механизм:

PERFORM pg_advisory_lock(id); 
... 
# do something that others must wait for 
... 
PERFORM pg_advisory_unlock(id); 

где id - значение BIGINT, которое я могу выбрать произвольно в соответствии с логикой моего приложения.

Это дало мне силу и гибкость, которые я искал.

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