处理 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 的演员在这里起到了作用。更多详情:

How to check if a table exists in a given schema

【讨论】:

感谢您的全面回答,但我要求进行异常处理而不是检查表是否存在,因为我面临竞争条件(不仅在这种最终不存在表的特定情况下)。我在官方 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 数据库时出现异常?

如何优雅地处理Async/Await的异常?

SpringBoot:如何优雅地处理全局异常?

Spring Boot 统一参数校验统一异常统一响应,这才是优雅的处理方式!

Spring Boot 统一参数校验统一异常统一响应,这才是优雅的处理方式!

使用优雅方式对参数验证进行处理