2017-02-17 7 views
6

В моем понимании PostgreSQL использует какие-то мониторы, чтобы угадать, есть ли конфликт в сериализованном уровне изоляции. Многие примеры касаются изменения одного и того же ресурса в параллельных транзакциях, и сериализуемая транзакция отлично работает. Но я хочу проверить параллельную проблему по-другому.Почему сериализуемая транзакция PostgreSQL считает это конфликтом?

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

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

create table accounts (
    id serial primary key, 
    user_id int, 
    type varchar, 
    balance numeric 
); 

insert into accounts (user_id, type, balance) values 
    (1, 'checking', 1000), 
    (1, 'saving', 1000), 
    (2, 'checking', 1000), 
    (2, 'saving', 1000); 

В таблице данные, как это:

id | user_id | type | balance 
----+---------+----------+--------- 
    1 |  1 | checking | 1000 
    2 |  1 | saving | 1000 
    3 |  2 | checking | 1000 
    4 |  2 | saving | 1000 

Теперь я бег 2 одновременной транзакции для 2 пользователей. В каждой транзакции я уменьшаю текущую учетную запись с некоторыми деньгами и проверяю общий баланс пользователя. Если оно больше 1000, то совершите, в противном случае откат.

1 пользователя в пример:

begin; 

-- Reduce checking account for user 1 
update accounts set balance = balance - 200 where user_id = 1 and type = 'checking'; 

-- Make sure user 1's total balance > 1000, then commit 
select sum(balance) from accounts where user_id = 1; 

commit; 

Пользователь 2 то же самое, за исключением user_id = 2 в where:

begin; 
update accounts set balance = balance - 200 where user_id = 2 and type = 'checking'; 
select sum(balance) from accounts where user_id = 2; 
commit; 

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

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. 

Мои вопросы:

  1. Почему PostgreSQL считает, что это 2 транзакции конфликт? Я добавил условие user_id для всех SQL и не изменяет user_id, но все это не влияет.
  2. Означает ли это, что сериализуемая транзакция не позволяет выполнять параллельные транзакции в одной таблице, даже если их чтение/запись не конфликтуют?
  3. Сделать что-то для каждого пользователя очень распространенным я могу избежать использования сериализуемой транзакции для операций, которые происходят очень часто?
+0

Возможно, вы захотите спросить об этом в списке рассылки Postgres –

ответ

5

Вы можете решить эту проблему с помощью следующего показателя:

CREATE INDEX accounts_user_idx ON accounts(user_id); 

Поскольку существует так мало данных в вашем примере таблицы, вы должны сказать PostgreSQL использовать сканирование индекса:

SET enable_seqscan=off; 

Теперь ваш пример будет работать!

Если это похоже на черную магию, взгляните на планы выполнения запросов своих SELECT и UPDATE.

Без индекса оба будут использовать последовательное сканирование в таблице, тем самым считывая все строки в таблице. Таким образом, обе транзакции будут иметь SIReadLock на всей таблице.

Это вызывает сбой сериализации.

+0

Итак, ключевой момент - избежать полного сканирования таблицы, а индекс не запускается, если данные таблицы слишком малы, правильно ли я понимаю? – darkbaby123

+0

Это точно. –

-2

Насколько мне известно, сериализуемый имеет самый высокий уровень изоляции, поэтому самый низкий уровень параллелизма. Транзакции происходят один за другим с нулевым параллелизмом.

+0

Почему PostgreSQL считает, что эти 2 транзакции конфликтуют? Я добавил условие user_id для всех SQL и не изменяет user_id, но все это не влияет. -> не уверен, почему у вас есть этот конфликтный приятель, может быть, когда он ищет записи, поскольку он имеет общее поле типа «проверка», может быть причиной. вы попробовали один с «сбережениями» и один с «проверкой» – sivalingam

+0

Я попробовал, все еще конфликтую. На самом деле это конфликт, даже когда все условия сводятся к 'where user_id = x'. – darkbaby123

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