用于序列化失败的PostgreSQL通用处理程序

时间:2015-03-31 15:17:35

标签: postgresql isolation-level transaction-isolation

这是one的后续问题所以我知道我可以使用(阻塞)LOCK,但我想使用谓词锁和可序列化的事务隔离。

我想拥有的是序列化失败的通用处理程序,它会重试函数/查询X次。

例如,我有这个:

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;

CREATE OR REPLACE FUNCTION myretest() RETURNS integer AS $$
DECLARE
    tries integer := 5;
BEGIN
    WHILE TRUE LOOP
        BEGIN -- nested block for exception
            RETURN mytest();
        EXCEPTION
            WHEN SQLSTATE '40001' THEN
                IF tries > 0 THEN
                    tries := tries - 1;
                    RAISE NOTICE 'Restart! % left', tries;
                ELSE
                    RAISE EXCEPTION 'NO RESTARTS LEFT';
                END IF;
        END;
    END LOOP;
END
$$
LANGUAGE plpgsql;

因此,如果直接同时调用mytest(),我会在最后一次提交时遇到序列化失败:

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

ERROR:  could not serialize access due to concurrent update
CONTEXT:  SQL statement "update account set balance = cc+10 where id=1 RETURNING balance"
PL/pgSQL function mytest() line 10 at SQL statement

如果我致电myretest(),它应该尝试执行mytest()直到第5次尝试它会引发异常。

所以我在这里有两点(也许第2点也使第1点无效):

  • myretest()无法按预期工作,即使在并发线程完成后,每次迭代都会导致serialiation_failure异常:是否应该添加一些内容" reset"交易?

  • 如何使这个(myretest()逻辑)通用,以便它适用于系统中的每个被调用函数,而不需要"包装器"这样的功能?

1 个答案:

答案 0 :(得分:1)

只要您在收到SQLSTATE 40001 40P01或{{1}}的错误时使用某个启动事务的框架,可序列化事务就能提供您正在寻找的内容。< / p>

在PostgreSQL中,函数总是在事务的上下文中运行。您无法在“包装器”功能的上下文中启动新事务。这需要稍微不同的功能,通常称为“存储过程” - 这在PostgreSQL中是不存在的。因此,您需要将逻辑管理重新启动到将数据提交到数据库的代码中。幸运的是,有许多连接器--Java,perl,python,tcl,ODBC等。甚至还有一个连接器,用于在PostgreSQL过程语言中单独连接到PostgreSQL数据库,这可能允许你做类似的事情。你想要什么:

http://www.postgresql.org/docs/current/static/dblink.html

我已经在各种“客户端”框架中看到过这种情况。很明显,将它传播到应用程序逻辑上处理数据库的所有位置是一个坏主意,但有很多很好的理由通过一种“访问器”方法(或至少非常少量的方法)路由所有数据库请求),大多数框架提供了在该层处理此问题的方法。 (例如,在Spring中,您可能希望使用依赖注入创建事务管理器。)这可能属于您用于应用程序逻辑的某种语言,但如果您真的想要,则可能使用plpgsql和dblink;但是,这可能不会是你最简单的道路。