【问题标题】:PostgreSQL transaction restartPostgreSQL 事务重启
【发布时间】:2015-06-04 16:09:35
【问题描述】:

我开始使用 PostgreSQL 并注意到 sequences 永远不会回滚,即使在失败的 INSERT 上也是如此。
我已经读到它可以防止并发事务中的重复序列,我发现这很奇怪,因为我的数据库经验仅适用于GTM,其中事务重新启动很常见并且正好用于此目的。

所以我想在 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 秒(让我启动另一个进程),然后根据保存的变量更新余额。

我现在直接从 shell 启动 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,然后提交的最后一个事务确实引发异常,但我想知道是否没有任何“事务重启”功能(如我所述)或者我是否错过了什么……

【问题讨论】:

    标签: postgresql transactions plpgsql


    【解决方案1】:

    如果您对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;
    

    这些最近的问题似乎遇到了类似的问题。详细解释及链接:

    【讨论】:

    • 锁与我想要/描述的行为不完全相同,但它是一种替代方法,我也不知道如何使用和适用于我的情况,因此接受作为答案,谢谢!而且我知道我可以将其简化为一次更新,但目的是轻松精确地测试它,看看它是否会“重新启动”。
    • 使用“restart”我的意思是第二个事务不会等待第一个事务完成(就像第一行的 LOCK 一样),但它会执行并且只有在 COMMIT 部分它会检查“原始数据库视图”已更改,结果将出乎意料,因此从 START TRANSACTION 点重新开始。
    • 将事务隔离级别设置为“可序列化”会执行此检查,但不会重新启动它会引发异常。我想我可以有一个外部函数以某种方式处理该异常,它会重新执行内部函数,直到它没有抛出它,但目前 LOCK 可以工作,甚至听起来像是更适合我的场景的解决方案。
    • 可序列化的隔离以备将来参考,以防有人在这里结束postgresql.org/docs/9.3/static/…
    • 严格来说,PostgreSQL 在READ COMMITTED 中锁定时不会像 Oracle(例如)那样重新启动查询。相反,它只重新读取锁定的行并继续。请参阅源代码中的EvalPlanQual 了解一些血腥细节。因此,您实际上可以获得基于重新启动的方法无法实现的异常。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-05-14
    • 2016-05-06
    • 1970-01-01
    • 2012-02-15
    • 2016-03-29
    相关资源
    最近更新 更多