提问者:小点点

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


这是这个问题的后续问题,所以我知道我可以使用(阻塞)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异常:是否应该添加一些东西来“重置”事务?

    我如何使这个(myretest()逻辑)通用,以便它适用于系统中的每个调用函数,而无需“包装”函数本身?


  • 共1个答案

    匿名用户

    只要您使用一些框架,在收到4000140P01SQLSTATE错误时启动事务,可序列化事务就会提供您正在寻找的内容。

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

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

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