用于序列化失败的 PostgreSQL 通用处理程序
PostgreSQL generic handler for serialization failure
这是此 的后续问题,所以我知道我可以使用(阻塞)锁,但我想使用谓词锁和可序列化事务隔离。
我想要的是一个序列化失败的通用处理程序,它将重试 function/query 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()
逻辑)通用,以便它可以应用于系统中的每个被调用函数而不需要 "wrapper" 函数本身?
只要您使用某种框架在收到 SQLSTATE
为 40001
或 40P01
的错误时启动事务,序列化事务就可以提供您正在寻找的内容.
在 PostgreSQL 中,函数总是在事务上下文中运行。您不能在 "wrapper" 函数的上下文中启动新事务。这将需要一个略有不同的功能,通常称为 "stored procedure" —— PostgreSQL 中不存在的东西。因此,您需要将管理重启的逻辑放入将事务提交到数据库的代码中。幸运的是,有许多连接器——Java、perl、python、tcl、ODBC 等。甚至还有一个连接器用于在 PostgreSQL 过程语言中单独连接到 PostgreSQL 数据库,这可能会让你做你想做的事:
http://www.postgresql.org/docs/current/static/dblink.html
我已经在各种 "client" 框架中看到过这样做。显然,将其传播到应用程序在逻辑上处理数据库的所有位置并不是一个好主意,但是有很多充分的理由通过一种 "accessor" 方法(或至少是非常小的数量)来路由所有数据库请求它们中的一个),并且大多数框架都提供了一种在该层处理此问题的方法。 (例如,在 Spring 中,您可能希望使用依赖注入创建一个事务管理器。)这可能属于您用于应用程序逻辑的某种语言,但如果您真的想要,您可以使用 plpgsql 和 dblink ;不过,这可能不是最简单的方法。
这是此
我想要的是一个序列化失败的通用处理程序,它将重试 function/query 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()
逻辑)通用,以便它可以应用于系统中的每个被调用函数而不需要 "wrapper" 函数本身?
只要您使用某种框架在收到 SQLSTATE
为 40001
或 40P01
的错误时启动事务,序列化事务就可以提供您正在寻找的内容.
在 PostgreSQL 中,函数总是在事务上下文中运行。您不能在 "wrapper" 函数的上下文中启动新事务。这将需要一个略有不同的功能,通常称为 "stored procedure" —— PostgreSQL 中不存在的东西。因此,您需要将管理重启的逻辑放入将事务提交到数据库的代码中。幸运的是,有许多连接器——Java、perl、python、tcl、ODBC 等。甚至还有一个连接器用于在 PostgreSQL 过程语言中单独连接到 PostgreSQL 数据库,这可能会让你做你想做的事:
http://www.postgresql.org/docs/current/static/dblink.html
我已经在各种 "client" 框架中看到过这样做。显然,将其传播到应用程序在逻辑上处理数据库的所有位置并不是一个好主意,但是有很多充分的理由通过一种 "accessor" 方法(或至少是非常小的数量)来路由所有数据库请求它们中的一个),并且大多数框架都提供了一种在该层处理此问题的方法。 (例如,在 Spring 中,您可能希望使用依赖注入创建一个事务管理器。)这可能属于您用于应用程序逻辑的某种语言,但如果您真的想要,您可以使用 plpgsql 和 dblink ;不过,这可能不是最简单的方法。