Postgresql 、openGauss系function函数Volatile 属性索引无法使用
最近的一个从oracle到Openguass的项目迁移时,应用反馈SQL在使用列等值右侧function返回值无法使用索引的问题,当把function换成字面量时可以正常使用索引对象,这个问题对PostgreSQL开发者很重要,因为许多数据库开发人员不知道PostgreSQL函数的Volatility(不稳定性)。每个函数都有一个Volatility分类,可能是VOLATILE、STABLE或IMMUTABLE。VOLATILE是默认值。该问题同样适用于openguass系的package中的function.
关于Function Volatility Categories描述可以参考官方文档。
Volatile 函数在调用中,输入同样的参数可能会返回不同的结果,每一行都会重新计算该函数。
Stable 函数单个Query中所有行给定同样的参数确保返回相同的结果。在索引扫描的条件中使用这种函数是可行的,因为索引扫描只计算一次比较值(comparison value),而不是每行都计算一次。
Immutable 函数在任何情况下,只要输入参数相同,返回结果就相同。这种级别的函数,优化器可以提前进行计算,在查询过程中作为常量参数。
这个问题在Oracle中的函数同样存在限制,如创建函数索引时要求必须是稳定的,要求的function带关键字”Deterministic” , 有些国产库为了兼容oracle也增加了这个函数的稳定性参数,如KingbaseES, 等同于pg系的immutable, 而oracle中的immutable关键字在19c(19.11)和21C 一个新类型的Immutable Tables 新特性时使用, Immutable Tables 是一个防篡改、只插入的表,具有相关联的表级和行级保留期。它们类似于区块链表,但是没有使用加密散列将行链接起来。no drop和no delete2个选项可以指定时间决定了表多久可以被drop或delete .
下面测试一下该现象, — env postgresql 13.2
[local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f1(id integer) anbob-# returns bigint anbob-# language 'sql' anbob-# as anbob-# $$ anbob$# select id+1; anbob$# $$ ; CREATE FUNCTION [local]:5432 postgres@anbob=# [local]:5432 postgres@anbob=# select f1(2); f1 ---- 3 (1 row) [local]:5432 postgres@anbob=# \d t1 Table "public.t1" Column | Type | Collation | Nullable | Default --------+-----------------------+-----------+----------+--------- id | integer | | not null | name | character varying(10) | | | Indexes: "cons_1234567890_1234567890_1234567890_1234567890_1234567890_123" PRIMARY KEY, btree (id) [local]:5432 postgres@anbob=# select * from t1; id | name ----+------ (0 rows) [local]:5432 postgres@anbob=# insert into t1 select x,'anbob' from generate_series(1,1000) as x; INSERT 0 1000 [local]:5432 postgres@anbob=# select * from t1; id | name ------+------- 1 | anbob 2 | anbob 3 | anbob 4 | anbob 5 | anbob 6 | anbob ... [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=11; QUERY PLAN ------------------------------------------------------------------------------------------------------------------ Index Scan using cons_1234567890_1234567890_1234567890_1234567890_1234567890_123 on t1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.014..0.016 rows=1 loops=1) Index Cond: (id = 11) Planning Time: 0.057 ms Execution Time: 0.030 ms (4 rows) [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f1(10); QUERY PLAN ------------------------------------------------------------------------------------------------------ Index Scan using cons_1234567890_1234567890_1234567890_1234567890_1234567890_123 on t1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.017..0.018 rows=1 loops=1) Index Cond: (id = '11'::bigint) Planning Time: 0.121 ms Execution Time: 0.038 ms (4 rows)
使用FUNCTION language SQL时,查询和字面常量一样,可以正常使用索引。
SQL VS PLPGSQL
[local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f2(id integer) anbob-# returns bigint anbob-# language 'sql' anbob-# as anbob-# $$ anbob$# select case when id>10 then 1 else 2 end; anbob$# $$ ; CREATE FUNCTION [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f2(10); QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------- Index Scan using cons_1234567890_1234567890_1234567890_1234567890_1234567890_123 on t1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.012..0.013 rows=1 loops=1) Index Cond: (id = '2'::bigint) Planning Time: 0.100 ms Execution Time: 0.031 ms (4 rows) [local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f2(id integer) anbob-# returns bigint anbob-# language 'plpgsql' anbob-# as anbob-# $$ anbob$# begin anbob$# return case when id>10 then 1 else 2 end; anbob$# end; anbob$# $$ ; CREATE FUNCTION [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f2(10); QUERY PLAN ----------------------------------------------------------------------------------------------- Seq Scan on t1 (cost=0.00..268.50 rows=1 width=10) (actual time=0.074..0.754 rows=1 loops=1) Filter: (id = f2(10)) Rows Removed by Filter: 999 Planning Time: 0.044 ms Execution Time: 0.768 ms (5 rows)
NOTE:
SQL VS PLPGSQL 不同, 函数中的逻辑一样,但是不同的language结果不同,language SQL优化器在执行时如果子查询返回给了主查询,可以正常使用索引。但是使用plpgsql时,没有使用索引而是全表扫seq scan on t1, 这就是本文开始时的应用当前面临的问题,当然应用中的逻辑比这里复杂,所以无法使用language SQL。那是否有方法可以使用索引还依赖Volatility属性。
Function Volatility
--DEFAULT VOLATILE [local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f_bigest(id1 integer, i d2 integer) anbob-# RETURNS bigint anbob-# AS anbob-# $$ anbob$# BEGIN anbob$# RETURN anbob$# CASE WHEN id1>=id2 THEN id1 ELSE id2 END; anbob$# END; anbob$# $$ LANGUAGE 'plpgsql'; CREATE FUNCTION [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f_bigest(10 , 11); QUERY PLAN ----------------------------------------------------------------------------------------------- Seq Scan on t1 (cost=0.00..268.50 rows=1 width=10) (actual time=0.036..0.734 rows=1 loops=1) Filter: (id = f_bigest(10, 11)) Rows Removed by Filter: 999 Planning Time: 0.045 ms Execution Time: 0.749 ms (5 rows) [local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f_bigest_stable(id1 integer, id2 integer) anbob-# RETURNS bigint anbob-# stable anbob-# AS anbob-# $$ anbob$# BEGIN anbob$# RETURN anbob$# CASE WHEN id1>=id2 THEN id1 ELSE id2 END; anbob$# END; anbob$# $$ LANGUAGE 'plpgsql'; CREATE FUNCTION [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f_bigest_stable(10 , 11); QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------- ---------- Index Scan using cons_1234567890_1234567890_1234567890_1234567890_1234567890_123 on t1 (cost=0.53..8.54 rows=1 width=10) (actual time=0.013..0.014 rows=1 loops=1) Index Cond: (id = f_bigest_stable(10, 11)) Planning Time: 0.141 ms Execution Time: 0.027 ms (4 rows) [local]:5432 postgres@anbob=# CREATE OR REPLACE FUNCTION f_bigest_immutable(id1 integer, id2 integer) anbob-# RETURNS bigint anbob-# immutable anbob-# AS anbob-# $$ anbob$# BEGIN anbob$# RETURN anbob$# CASE WHEN id1>=id2 THEN id1 ELSE id2 END; anbob$# END; anbob$# $$ LANGUAGE 'plpgsql'; CREATE FUNCTION [local]:5432 postgres@anbob=# explain analyze select * from t1 where id=f_bigest_immutable(10 , 11); QUERY PLAN ----------------------------------------------------------------------------------------------------------------------------------------------------------- ---------- Index Scan using cons_1234567890_1234567890_1234567890_1234567890_1234567890_123 on t1 (cost=0.28..8.29 rows=1 width=10) (actual time=0.008..0.009 rows=1 loops=1) Index Cond: (id = '11'::bigint) Planning Time: 0.119 ms Execution Time: 0.020 ms (4 rows)
NOTE:
可见除了VOLATILE(未指定,默认) function 无法使用索引外,stable和immutable的 函数都可以正常使用index, 但是在index 谓词上immutable使用的是函数转换后的常量值,而stable使用的是function .
属性修改
[local]:5432 postgres@anbob=# select proname,pronamespace,proowner,provolatile anbob-# --propackage,propackageid, anbob-# from pg_proc where proname like 'f_bigest%'; proname | pronamespace | proowner | provolatile --------------------+--------------+----------+------------- f_bigest | 2200 | 10 | v f_bigest_stable | 2200 | 10 | s f_bigest_immutable | 2200 | 10 | i (3 rows) [local]:5432 postgres@anbob=# alter function f_bigest stable; ALTER FUNCTION [local]:5432 postgres@anbob=# select proname,pronamespace,proowner,provolatile --propackage,propackageid, from pg_proc where proname like 'f_bigest%'; proname | pronamespace | proowner | provolatile --------------------+--------------+----------+------------- f_bigest | 2200 | 10 | s f_bigest_stable | 2200 | 10 | s f_bigest_immutable | 2200 | 10 | i (3 rows)
Note:
function的Volatility除了重建时指定,也可以使用alter function修改,通过pg_proc.provolatile字典可以查询, 在opengauss中增加了package, package中的function创建时同样可以指定Volatility, 但是package中的function目前没有语法直接修改,而裸function一样可以修改,不过需要指定”()”和参数变量确定唯一。
对不起,这篇文章暂时关闭评论。