SQL 函数内联
SQL 函数内联
SQL 函数(即 LANGUAGE SQL
)在某些情况下,会将其函数体内联到调用查询中,而不是直接调用。这可以带来显著的性能优势,因为函数体被暴露给调用查询的规划器,规划器可以应用诸如常量折叠、谓词下推等优化。
然而,适用于内联的精确条件比较复杂,并且在源代码之外没有得到很好的记录。本页面旨在部分解决这个问题。
这里列出的规则被认为在 8.4 到 9.5 版本的 pg 中是正确的。
实际上,存在两种完全独立的内联形式,对于任何给定的函数调用,最多只能发生其中一种:一种用于*标量函数调用*,另一种用于*表函数调用*。
标量函数
标量函数调用是指在值表达式或谓词的上下文中出现 func(args)
的任何情况,即在任何普通值或条件可以出现的地方。例如
select func(t.foo) from sometable t;
select t.* from sometable t where func(t.foo, 123);
在上面的第二个例子中,如果 func()
的定义对第一个参数应用了可索引的操作符,那么内联对于性能至关重要,因为它允许规划器使用索引。这种方法在 PostGIS 中被广泛使用,用于诸如 ST_Contains
和 ST_DWithin
之类的函数。
表函数
表函数调用是指在预期出现表的任何地方出现 func(args)
的情况。(对于大多数函数来说,这是 PostgreSQL 对 SQL 标准的扩展。)例如
select * from func(123);
select * from sometable t, func(t.foo, 123); -- version 9.3+, implicitly LATERAL
表函数调用内联的最重要的性能优势之一是能够将额外的条件下推到函数中
select * from func(123) as f where f.foo=456;
在这个例子中,在内联 func(123)
之后,规划器可能能够将 f.foo
条件移到函数体内,从而可能将其用作函数引用的表的索引条件。
标量函数内联条件
如果满足以下所有条件,则会对标量函数调用进行内联(源代码)
- 该函数为
LANGUAGE SQL
- 该函数不是
SECURITY DEFINER
- 该函数不是
RETURNS SETOF
(或RETURNS TABLE
)
- 该函数不是
RETURNS RECORD
- 该函数在其定义中没有
SET
子句
- 该函数没有被内联;递归函数只扩展其最外层的调用为内联
- 没有插件模块挂钩函数进入/退出调用
- 函数体由一个简单的
SELECT expression
组成
- 函数体不包含聚合或窗口函数调用、子查询、CTE、
FROM
子句或对任何表或类似表的对象的引用,也不包含GROUP BY
、HAVING
、ORDER BY
、DISTINCT
、LIMIT
、OFFSET
、UNION
、INTERSECT
、EXCEPT
- 函数体查询必须返回恰好一列
- 函数体表达式的类型必须与函数声明的返回类型匹配
- 表达式不能返回多行(例如,从调用诸如
unnest()
或generate_series()
之类的返回集函数)
- 如果函数声明为
IMMUTABLE
,则表达式不能调用任何不可变函数或操作符
- 如果函数声明为
STABLE
,则表达式不能调用任何易变函数或操作符
- 如果函数声明为
STRICT
,则规划器必须能够证明函数体表达式在任何参数为 null 时都必然返回NULL
。目前,只有在满足以下条件时才满足此条件:每个参数至少被引用一次,并且函数体中使用的所有函数、操作符和其他结构本身都是STRICT
。
- 如果函数调用的实际参数是易变表达式,则它在函数体中引用的次数不能超过一次
- 如果实际参数是“昂贵”表达式,定义为成本超过 10 个操作符成本或包含任何子查询,则它在函数体中引用的次数不能超过一次
表函数内联条件
如果满足以下所有条件,则会对表函数调用进行内联(源代码)
- 函数调用未指定
ORDINALITY
或在ROWS FROM
中指定多个函数
- 实际参数都不包含易变表达式或子选择
- 没有插件模块挂钩函数调用进入/退出
- 该函数为
LANGUAGE SQL
- 该函数不是
SECURITY DEFINER
- 该函数声明为
STABLE
或IMMUTABLE
- 该函数未声明为
STRICT
- 该函数声明为
RETURNS SETOF
或RETURNS TABLE
- 该函数在其定义中没有
SET
子句
- 函数体必须由一个
SELECT
语句组成,即使在应用了任何适用的规则重写之后也是如此
- 函数体查询的列的返回类型必须与声明的结果类型的列匹配。但是,如果顶层函数体查询中没有集合运算(
UNION
、INTERSECT
、EXCEPT
),则允许调整删除的列,并且允许隐式二进制强制转换,前提是受影响的列未用于排序
- 如果函数声明为
RETURNS SETOF RECORD
且没有OUT
参数,则函数体查询结果类型必须与调用者提供的列定义列表匹配
请注意,这些条件允许函数体为一个复杂的查询,甚至是一个包含 CTE(但不包含 wCTE)的查询。这使得可内联的 SQL 函数比视图更强大,因为它们可以以视图无法实现的方式接受参数,同时保留了视图的许多优势。