错误和异常处理

366
2023/08/01 17:18:09

本章我们将讨论 PL/pgSQL 的错误和异常处理。

报告消息

使用 raise 语句可以发出消息:

  • 级别:指定严重性,可选值为:
    • debug
    • log
    • notice
    • info
    • warning
    • exception(默认值)
  • 格式:消息的字符串,其中的占位符% 将被参数的值替换
    • 占位符的数量必须和参数的数量一致

以下示例演示了不同级别下,显示当前时间的消息:

do $$ 
begin 
  raise info 'information message %', now() ;
  raise log 'log message %', now();
  raise debug 'debug message %', now();
  raise warning 'warning message %', now();
  raise notice 'notice message %', now();
end $$;

并非所有消息都会报告给客户端。默认情况下,PostgreSQL 仅报告 infowarningnotice 级别的消息。这是由 client_min_messageslog_min_message 配置决定的。

抛出错误

使用 raise exception 即可抛出错误,这也是 raise 的默认级别(如果没有提供 级别 参数的话)。

raise 还可以使用以下可选子句来附加更多信息:

USING 选项 = 表达式;

选项 的可选值有:

  • message :设置错误消息
  • hint:提供提示信息,以便容易定位错误
  • detail:给出有关错误的详细信息
  • errcode:错误代码
do $$ 
declare
  email varchar(255) := '[email protected]';
begin 
  -- 检查邮箱是否重复
  -- ...
  -- 报告邮箱重复
  raise exception '邮箱重复:%', email 
		using hint = '请检查邮箱地址';
end $$;

断言

ASSERT 语句是将调试检查插入到 PL/pgSQL 的实用的简便方法。语法如下:

assert 条件 [, 提示信息];
  • 条件:是一个布尔表达式,预期返回 TRUE。如果它的计算结果为 TRUE,则 ASSERT 不执行任何操作;否则(NULLFALSE),会引发 assert_failure 异常
  • 提示信息:可选参数。如果不传递该参数,PostgreSQL 使用默认的 assertion failed 消息;否则使用该参数作为提示信息。

assert 应该只用于检测错误,而不是报告错误。如果要报告错误,应使用 raise

通过 plpgsql.check_asserts 配置来启用或关闭断言。如果将该配置项设置为 off,将关闭断言。

以下示例使用 assert 来检查示例数据中 film 表是否有数据:

do $$
declare 
   film_count integer;
begin
   select count(*)
   into film_count
   from film;
   
   assert film_count > 0, '没有影片';
end$$;

异常处理

当块中发生错误时,PostgreSQL 将中止该块及周围事务的执行。要从错误中恢复,可以在 BEGIN...END 块中使用 EXCEPTION 子句。

其语法如下:

<<label>>
declare
begin
    statements;
exception
    when condition [or condition...] then
       handle_exception;
   [when condition [or condition...] then
       handle_exception;]
   [when others then
       handle_other_exceptions;
   ]
end;

以下示例会发生错误,因为 ID 为 2000 的电影不存在,它会抛出 no_data_found 异常。通过使用 EXCEPTION 捕获后,进行更有意义的处理:

do $$
declare
	rec record;
	v_film_id int = 2000;
begin 
	select film_id, title 
	into strict rec
	from film
	where film_id = v_film_id;
        -- 捕获异常
	exception 
	   when no_data_found then 
	      raise exception '电影 % 不存在', v_film_id;
end $$;

以下示例演示了如何处理 too_mary_rows 异常:

do $$
declare
	rec record;
begin 
	select film_id, title 
	into strict rec
	from film
	where title LIKE 'A%';
	
	exception 
	   when too_many_rows then
	      raise exception '查询结果返回多行';
end $$;

以下示例演示了如何捕获多个异常:

do $$
declare
	rec record;
	v_length int = 90;
begin
	select film_id, title 
	into strict rec
	from film
	where length = v_length;
	exception 
	   when sqlstate 'P0002' then 
	      raise exception 'film with length % not found', v_length;
	   when sqlstate 'P0003' then 
	      raise exception 'The with length % is not unique', v_length;
end $$;