2015-03-31 2 views
2

Я начинаю играть с PostgreSQL и замечаю, что sequence никогда не откатывается, даже при неудачном INSERT.
Я читал, что, как и ожидалось, будет предотвращено дублирование последовательностей в параллельных транзакциях, и я обнаружил, что это странно, поскольку мой опыт работы с базой данных только с GTM, где перезапуск транзакций является общим и используется именно для этого.Перезагрузка транзакции PostgreSQL

Так что я хотел проверить перезагружается в PGSQL и загружается это в базе данных:

CREATE SEQUENCE account_id_seq; 

CREATE TABLE account 
(
    id integer NOT NULL DEFAULT nextval('account_id_seq'), 
    title character varying(40) NOT NULL, 
    balance integer NOT NULL DEFAULT 0, 
    CONSTRAINT account_pkey PRIMARY KEY (id) 
); 

INSERT INTO account (title) VALUES ('Test Account'); 

CREATE OR REPLACE FUNCTION mytest() RETURNS integer AS $$ 
DECLARE 
    cc integer; 
BEGIN 
    cc := balance from account where id=1; 

    RAISE NOTICE 'Balance: %', cc; 
    perform pg_sleep(3); 

    update account set balance = cc+10 where id=1 RETURNING balance INTO cc; 

    return cc; 
END 
$$ 
LANGUAGE plpgsql; 

Таким образом, функция mytest() будет извлекать баланс, подождать 3 секунды (чтобы позволить мне запустить другой процесс), а затем обновить баланс на основе сохраненной переменной.

теперь запустить 2 вызова этой функции непосредственно из оболочки:

void$ psql -c "select * from account where id=1" 
id | title  | balance 
----+--------------+--------- 
    1 | Test Account |  0 
(1 row) 

void$ psql -c "select mytest()" & PIDA=$! && psql -c "select mytest()" && wait $PIDA 
[1] 3312 
NOTICE: Balance: 0 
NOTICE: Balance: 0 
mytest 
-------- 
    10 
(1 row) 

mytest 
-------- 
    10 
(1 row) 

[1]+ Done     psql -c "select mytest()" 
void$ psql -c "select * from account where id=1" 
id | title  | balance 
----+--------------+--------- 
    1 | Test Account |  10 
(1 row) 

я ожидал бы баланс, чтобы быть 20, а не 10, как последняя сделка будет совершено необходимо перезапустить в качестве «зрения» balance from account where id=1 изменено в процессе обработки ...

Я читал о transaction isolation in official documentation, и это кажется мне, что по умолчанию read committed должно обеспечивать такое поведение точно ..
Я также испытанное изменяя уровень изоляции на serializable, а затем последний Tran исправлено, но я хотел бы знать, нет ли какой-либо функции «перезагрузки транзакции» (как я описал), или если что-то не хватает ...

ответ

2

Вы автоматически получаете «перезапуск», если вы используете правильные запросы с row level locks. Чтобы быть точным, то сделка не будет возобновлено в целом, он просто ждет своей очереди при попытке заблокировать строку в default transaction isolation READ COMMITTED:

CREATE OR REPLACE FUNCTION mytest() 
    RETURNS integer AS 
$func$ 
DECLARE 
    cc integer; 
BEGIN 
    SELECT INTO cc balance FROM account WHERE id = 1 FOR UPDATE; 

    RAISE NOTICE 'Balance: %', cc; 
    PERFORM pg_sleep(3); 

    UPDATE account SET balance = cc+10 
    WHERE id = 1 
    RETURNING balance 
    INTO cc; 

    RETURN cc; 
END 
$func$ LANGUAGE plpgsql;

SELECT ... FOR UPDATE берет блокировку на уровне строк, чтобы объявить требование, что эта строка собирается обновляться. Одна и та же функция, пытающаяся сделать то же самое в другой транзакции, будет заблокирована и дождитесь, пока первая не совершит или откатится, - затем возьмите сам замок и постройте обновленную строку, чтобы результат вашего эксперимента был равен 20, а не 10.

Вы можете иметь один и те же много более эффективны с помощью простого и простого UPDATE запроса, который принимает соответствующий FOR UPDATE locks автоматически:

UPDATE account 
SET balance = balance + 10 
WHERE id = 1 
RETURNING balance; 

этих последние вопросы, похоже, столкнулся с подобными проблемами. Подробное описание и ссылки:

+0

замки не точно так же, как и поведение я хотел/описал, но это была альтернатива, что я не знаю, как использовать и работает для моего дела, поэтому принято как ответ, спасибо! И я знаю, что я мог бы упростить его в одно обновление, но цель заключалась в том, чтобы легко протестировать это, чтобы увидеть, будет ли он «перезагружаться». –

+0

С «перезагрузкой» я имел в виду, что вторая транзакция не дождалась завершения первой (например, LOCK в первой строке), но она будет выполняться, и только в части COMMIT она проверит, что «исходный вид БД» изменился и результаты были бы неожиданными, поэтому перезапуск с точки START TRANSACTION. –

+0

Установка уровня изоляции транзакций на «serializable» делает это, но вместо перезапуска он выдает исключение. Я предполагаю, что я мог бы использовать функцию outter для этого исключения таким образом, чтобы она повторно выполняла внутреннюю функцию, пока она не выбросила ее, но на данный момент LOCK работают и даже кажутся лучшим решением для моего сценария. –

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