暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

POSTGRESQL V14中更好的SQL函数

原创 eternity 2022-08-03
336

我们都知道并重视SQL函数作为一种便捷的快捷方式。PostgreSQL v14引入了一种新的、更好的编写SQL函数的方法。本文将展示新语法的优点。

SQL函数示例

让我们创建一个具有“经典”语法的SQL函数的简单示例,以便我们有一些用于演示的材料:

CREATE EXTENSION unaccent;
 
CREATE FUNCTION mangle(t text) RETURNS text
   LANGUAGE sql
   AS 'SELECT lower(unaccent(t))';

您可以像使用其他数据库函数一样使用新函数:

SELECT mangle('Schön dumm');
 
   mangle   
════════════
 schon dumm
(1 row)

为什么使用SQL函数?

你可能会问SQL函数有多好。毕竟,数据库函数的主要目的是能够在数据库内运行过程代码,而这是SQL无法做到的。但SQL函数有其用途:

  • 不同SQL语句中常用表达式的代码重用

  • 通过将部分代码分解成具有有意义名称的函数,使SQL语句更具可读性

  • 无论何时出于语法原因需要函数,例如在CREATE AGGREGATE或CREATE OPERATOR中

此外,简单的SQL函数可以内联,也就是说,优化器可以在查询规划时用函数定义替换函数调用。这可以使SQL函数效率极高:

  • 它消除了实际函数调用的开销

  • 由于函数(大部分)是优化器的黑匣子,用其定义替换函数通常可以提供更好的估计

如果在示例函数中使用EXPLAIN(VERBOSE),我们可以看到函数内联:

EXPLAIN (VERBOSE, COSTS OFF) SELECT mangle('Schön dumm');
 
                  QUERY PLAN                   
═══════════════════════════════════════════════
 Result
   Output: lower(unaccent('Schön dumm'::text))
(2 rows)

PostgreSQL函数的缺点

PostgreSQL函数很棒。一个很好的方面是,您不局限于一种编程语言。PostgreSQL支持用SQL、C、PL/pgSQL(Oracle PL/SQL的克隆)、Perl、Python和Tcl编写的函数。但这并不是全部:在PostgreSQL中,您可以编写一个插件,允许您在数据库中使用您选择的任何语言。为了实现这种灵活性,PostgreSQL函数的函数体只是一个字符串常量,当PostgreSQL执行函数时,过程语言的调用处理程序会对其进行解释。这有一些不良副作用:

缺乏依赖项跟踪

通常,PostgreSQL跟踪pg_depend和pg_shdepend目录表中数据库对象之间的依赖关系。这样,数据库就知道了对象之间的关系:它要么阻止您删除其他对象依赖的对象(如带有外键引用的表),要么自动删除依赖对象(如删除表会删除表上的所有索引)。

由于函数体只是PostgreSQL无法解释的字符串常量,因此它不会跟踪函数和函数中使用的对象之间的依赖关系。过程语言可以提供一个验证器,用于检查函数体的语法正确性(如果check\u function\u bodies=on)。验证器还可以测试函数中引用的对象是否存在,但它不能阻止您稍后删除函数使用的对象。

让我们用我们的例子来演示:

DROP EXTENSION unaccent;
 
SELECT mangle('boom');
ERROR:  function unaccent(text) does not exist
LINE 1: SELECT lower(unaccent(t))
                     ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
QUERY:  SELECT lower(unaccent(t))
CONTEXT:  SQL function "mangle" during inlining

我们将通过再次创建扩展来解决问题。然而,当我们在不使用级联选项的情况下运行DROP扩展时,最好得到一条错误消息。

搜索路径作为安全问题

由于PostgreSQL在查询执行时解析函数体,因此它使用当前的search\u path设置来解析对不符合模式名称的数据库对象的所有引用。这不仅限于表和视图,还扩展到函数和运算符。我们可以使用示例函数来演示该问题:

SET search_path = pg_catalog;
 
SELECT public.mangle('boom');
ERROR:  function unaccent(text) does not exist
LINE 1: SELECT lower(unaccent(t))
                     ^
HINT:  No function matches the given name and argument types. You might need to add explicit type casts.
QUERY:  SELECT lower(unaccent(t))
CONTEXT:  SQL function "mangle" during inlining

在我们的例子中,这仅仅是一种烦扰,我们可以通过使用public来避免。函数调用中的uncent()。但它可能比这更糟糕,尤其是对于安全定义函数。由于模式限定每个函数和运算符很麻烦,建议的解决方案是在函数上强制搜索路径:

ALTER FUNCTION mangle(text)SET search_path=public;

请注意,search_path上的模式应该只允许特权用户创建,因此在v15之前的版本中,上面的内容不是一个好主意!

设置search_path的一个不愉快的缺点是它会阻止SQL函数的内联。

PostgreSQL v14中新的SQL函数语法

从PostgreSQL v14开始,SQL函数和过程的主体不再需要是字符串常量。现在,您可以对函数体使用以下形式之一:

CREATE FUNCTION function_name(...) RETURNS ...
RETURN expression;
 
CREATE FUNCTION function_name(...) RETURNS ...
BEGIN ATOMIC
   statement;
   ...
END;

第一种形式要求函数体是表达式。因此,如果要执行查询,必须将其括在括号中(将其转换为子查询,这是一个有效的表达式)。例如:

CREATE FUNCTION get_data(v_id bigint) RETURNS text
RETURN (SELECT value FROM data WHERE is = v_id);

第二种形式允许您使用多个SQL语句编写函数。与以前的多语句SQL函数一样,函数的结果将是最终SQL语句的结果。您还可以使用第二种形式的新语法来创建SQL过程。第一种形式显然不适用于过程,因为过程没有返回值。

我们可以轻松地重写示例函数以使用新语法:

CREATE OR REPLACE FUNCTION mangle(t text) RETURNS text
RETURN lower(unaccent(t));

注意,这些新的SQL函数可以像旧的一样内联到SQL语句中!

新SQL函数语法的优点

主要区别在于,新型SQL函数和过程在函数定义时进行解析,并以解析后的形式存储在pg_proc系统目录的prosqlbody列中。因此,上述两个缺点消失了:

使用新型SQL函数进行依赖项跟踪

由于函数体以解析形式提供,PostgreSQL可以跟踪依赖项。让我们用重新定义的示例函数来试试:

DROP EXTENSION unaccent;
ERROR:  cannot drop extension unaccent because other objects depend on it
DETAIL:  function mangle(text) depends on function unaccent(text)
HINT:  Use DROP ... CASCADE to drop the dependent objects too.

使用新型SQL函数修复了search_path

search_path仅在解析SQL时相关。由于这种情况现在在CREATE函数运行时发生,因此我们不必担心该参数在函数执行时的当前设置:

SET search_path = pg_catalog;
 
SELECT public.mangle('Schön besser');
 
    mangle    
══════════════
 schon besser
(1 row)

交互式客户端的问题

您可能注意到,用于定义SQL函数的多语句表单包含分号以终止SQL语句。这不仅会混淆像HeidiSQL(从未学习过美元引号)这样的常见嫌疑犯,而且对于任何将分号识别为SQL语句之间分隔符的客户机来说都是一个问题。即使是旧版本的psql也存在语法问题:

psql (13.7, server 15beta2)
WARNING: psql major version 13, server major version 15.
         Some psql features might not work.
Type "help" for help.
 
test=> CREATE FUNCTION tryme() RETURNS integer
BEGIN ATOMIC
   SELECT 42;
END;
ERROR:  syntax error at end of input
LINE 3:    SELECT 42;
                     ^
WARNING:  there is no transaction in progress
COMMIT

psql认为“SELECT 42”后的分号终止CREATE FUNCTION语句。截断的语句导致错误。最后一个结束被视为它自己的语句,它是COMMIT的同义词并引起警告。

在v14及更高版本中,psql正确处理此类语句。pgAdmin 4已经学习了6.3版本的新语法。但我确信,还有许多客户端尚未收到消息。

结论

PostgreSQL v14引入的新的SQL函数语法在可用性和安全性方面具有很大优势。获取一个支持新语法的客户端,并开始将其用于SQL函数。您应该考虑重写现有函数以利用这些优点。

阅读另一篇提高PostgreSQL语法理解能力的文章:我关于PostgreSQL中交叉连接的文章。

通过Hans关于如何查找和修复缺失索引的帖子,了解如何使PostgreSQL数据库发挥最大性能的更多信息。

原文标题:BETTER SQL FUNCTIONS IN POSTGRESQL V14
原文作者:Laurenz Albe
原文链接:https://www.cybertec-postgresql.com/en/better-sql-functions-in-postgresql-v14/

「喜欢这篇文章,您的关注和赞赏是给作者最好的鼓励」
关注作者
【版权声明】本文为墨天轮用户原创内容,转载时必须标注文章的来源(墨天轮),文章链接,文章作者等基本信息,否则作者和墨天轮有权追究责任。如果您发现墨天轮中有涉嫌抄袭或者侵权的内容,欢迎发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论