处理 PostgreSQL 异常的优雅方式?
Posted
技术标签:
【中文标题】处理 PostgreSQL 异常的优雅方式?【英文标题】:Elegant way of handling PostgreSQL exceptions? 【发布时间】:2015-04-10 19:04:50 【问题描述】:在 PostgreSQL 中,我想创建一个安全包装机制,如果发生异常,它会返回空结果。考虑以下几点:
SELECT * FROM myschema.mytable;
我可以在客户端应用程序中进行安全包装:
try
result = execute_query('SELECT value FROM myschema.mytable').fetchall();
catch(pg_exception)
result = []
但是我可以直接在 SQL 中做这样的事情吗?我想让下面的代码工作,但它似乎应该被放入 DO $$ ... $$
块中,我在这里迷路了。
BEGIN
SELECT * FROM myschema.mytable;
EXCEPTION WHEN others THEN
SELECT unnest(ARRAY[]::TEXT[])
END
【问题讨论】:
DO
无法返回任何内容。您必须为此使用存储过程(使用语言plpgsql
- 用于异常处理)。 -- 还有没有返回行不等于返回单行,里面有一个空数组(即在js中,这意味着[] != [col1:[]]
)。
@posz 抱歉,我在示例中添加了unnest
以产生所需的行为(不知道如何更优雅地执行此操作)。无论如何,每次执行带有异常处理的查询时,我是否必须声明过程?没有其他选择吗?
所以您想防御任何和所有异常,或者您只是担心该表可能不存在?你想返回什么?来自单个列 value
的单个值?还是一组行?这是针对一个硬编码表名还是针对各种可能的表名?
@Tregoreg 不,如果您想仅对简单查询进行异常处理,这通常由客户端完成,而不是服务器。 plpgsql
的异常处理主要针对存储的“逻辑”。但是你害怕什么类型的异常?也许,还有其他选择。
@posz 实际上,在我的具体情况下,我非常关心竞争条件。我正在使用SELECT ('myschema','mytable') IN (SELECT table_schema,table_name FROM information_schema.tables);
检查表是否存在,如果存在,我将选择它的行。但是经常发生在执行第二个查询之前表不存在的情况(即,成语“如果表存在,则选择其内容”不起作用)。这就是为什么我希望在发生异常时简单地返回空结果。
【参考方案1】:
PL/pgSQL 中的异常处理
一般来说,plpgsql 代码总是被包装在一个BEGIN .. END
块中。这可以在DO
语句或函数的主体内。块可以嵌套在内部 - 但它们不能存在于外部,不要将其与普通 SQL 混淆。
每个BEGIN
块都可以选择性地包含一个EXCEPTION
子句来处理异常,但是需要捕获异常的函数的开销要大得多,因此最好事先避免异常。
更多信息:
The manual on how to trap errors (handle exceptions) in PL/pgSQL
示例:Is SELECT or INSERT in a function prone to race conditions?
Search for related answers on SO
如何避免示例中的异常
DO
语句不能返回任何内容。 Create a function 将表和模式名称作为参数并返回您想要的任何内容:
CREATE OR REPLACE FUNCTION f_tbl_value(_tbl text, _schema text = 'public')
RETURNS TABLE (value text)
LANGUAGE plpgsql AS
$func$
DECLARE
_t regclass := to_regclass(_schema || '.' || _tbl);
BEGIN
IF _t IS NULL THEN
value := ''; RETURN NEXT; -- return single empty string
ELSE
RETURN QUERY EXECUTE
'SELECT value FROM ' || _t; -- return set of values
END IF;
END
$func$;
呼叫:
SELECT * FROM f_tbl_value('my_table');
或者:
SELECT * FROM f_tbl_value('my_table', 'my_schema');
假设您想要一组包含单个 text
列的行,或者如果表不存在则为空字符串。
如果给定的表存在,还假设存在列value
。您也可以对此进行测试,但您没有要求这样做。
两个输入参数仅在双引号中区分大小写。就像identifiers are handled in SQL statements。
在我的示例中,架构名称默认为 'public'
。适应您的需求。您甚至可以完全忽略架构并默认为当前的search_path
。
to_regclass()
是 Postgres 9.4 中的新功能。对于旧版本的替代品:
IF EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = _schema
AND table_name = _tbl
);
这实际上更准确,因为它准确地测试了您需要的内容。更多选项和详细解释:
Table name as a PostgreSQL function parameter在使用动态 SQL 时始终防御 SQL 注入! regclass
的演员在这里起到了作用。更多详情:
【讨论】:
感谢您的全面回答,但我要求进行异常处理而不是检查表是否存在,因为我面临竞争条件(不仅在这种最终不存在表的特定情况下)。我在官方 psql 文档中看到了用于模拟 UPSERT 的异常处理:postgresql.org/docs/current/static/… 这就是导致我要求优雅异常处理的原因。 @Tregoreg:添加了一些关于一般异常处理的内容。 我最终在DO
块中找到了BEGIN .. END
,用于我的INSERT
语句,其中很容易出现竞争条件,我实际上不需要选择任何东西。我做了一些基准测试,似乎以这种方式处理异常只需大约 1ms,这对我来说完全令人满意。 @ErwinBrandsetter 感谢您的清晰解释并强调纯 SQL 和 plpgsql 之间的区别,这就是我所缺少的。
@ErwinBrandstetter 我试了几次。我什至放弃了与互联网朋友的链接。似乎无法创建 f_tbl_value
函数。 ERROR: syntax error at end of input LINE 13: $func$ LANGUAGE plpgsql;
我不知道为什么。
@JianHe:我修正了语法错误。【参考方案2】:
如果您只选择一列,那么 COALESCE() 函数应该能够为您解决问题
SELECT COALESCE( value, ''::text[] ) FROM myschema.mytable
如果您需要更多行,您可能需要创建一个带类型的函数。
【讨论】:
实际上,我的示例可能有点误导。我的意思是更笼统的,表实际上可能会丢失(因此抛出异常)。我要问的是异常处理。以上是关于处理 PostgreSQL 异常的优雅方式?的主要内容,如果未能解决你的问题,请参考以下文章
使用 wt c++ 库连接到 postgresql 数据库时出现异常?
Spring Boot 统一参数校验统一异常统一响应,这才是优雅的处理方式!