KingbaseES函数三态

  1. 理解函数的三态1

    VOLATILE:

     volatile函数没有限制,可以修改数据(如执行delete,insert,update), 使用同样的参数调用可能返回不同的值.
    

    STABLE:

     不允许修改数据, PG8.0以及以上版本不允许在volatile函数中使用非SELECT|PERFORM语句.
     使用同样的参数调用返回同样的结果,在事务中有这个特性的也归属stable.
    

    IMMUTABLE:

      不允许修改数据, 使用同样的参数调用返回同样的结果.
    

    备注:Pg 8.0以及以上版本不允许在stable或immutable函数中执行非select|perform语句.

    示例:

     test=# create table tbl(id int primary key, info text, crt_time timestamp);
     CREATE TABLE
     test=# create or replace function f_tbl(i_id int) returns void as $$
     test$# declare
     test$# begin
     test$# update tbl set crt_time=now() where id=i_id;
     test$# end;
     test$# $$ language plpgsql stable;
     CREATE FUNCTION
     test=# \set VERBOSITY verbose
     test=# select f_tbl(1);
     ERROR:  0A000: UPDATE is not allowed in a non-volatile function
     背景:  SQL statement "update tbl set crt_time=now() where id=i_id"
     PL/pgSQL function f_tbl(integer) line 4 at SQL statement
     位置:  _SPI_execute_plan_internal, spi.c:2951
    

    漏洞:在stable或immutable函数中调用volatile函数是可以的.

    示例:

     test=# alter function f_tbl(int) volatile; 
     ALTER FUNCTION
     test=# create or replace function f_tbl1(i_id int) returns void as $$ 
     test$# declare
     test$# begin
     test$# perform f_tbl(i_id); -- 在stable或immutable函数中调用volatile函数是可以的.
     test$# end;
     test$# $$ language plpgsql stable;
     CREATE FUNCTION
     test=# 
     test=# insert into tbl values(1,'test',now());
     INSERT 0 1
     test=# select * from tbl;
      id | info |          crt_time          
     ----+------+----------------------------
       1 | test | 2022-11-10 11:46:05.854350
     (1 行记录)
     
     test=# select f_tbl1(1);
      f_tbl1 
     --------
      
     (1 行记录)
     
     test=# select * from tbl;
      id | info |          crt_time          
     ----+------+----------------------------
       1 | test | 2022-11-10 11:46:33.153699
     (1 行记录)
    

    同样的参数多次调用.

    volatile 函数相同的参数,多次调用返回结果可能不一样.

     kbstattuple扩展插件可以参考KingbaseES在线手册
     https://help.kingbase.com.cn/v8/development/sql-plsql/ref-extended-plug-in/kbstattuple.html?highlight=kbstattuple
     
     test=# create extension kbstattuple ;
     CREATE EXTENSION
     test=# \dx+ kbstattuple ;
           对象用于扩展 "kbstattuple"
                    对象描述                
     ---------------------------------------
      function pg_relpages(regclass)
      function pg_relpages(text)
      function pgstatginindex(regclass)
      function pgstathashindex(regclass)
      function pgstatindex(regclass)
      function pgstatindex(text)
      function pgstattuple_approx(regclass)
      function pgstattuple(regclass)
      function pgstattuple(text)
     (9 行记录)
     
     test=# create table t2(id int);
     CREATE TABLE
     test=# select pg_relpages('t2');
      pg_relpages 
     -------------
                0
     (1 行记录)
     
     test=# insert into t2 values (1);
     INSERT 0 1
     test=# select pg_relpages('t2');    -- 返回值变化
      pg_relpages 
     -------------
                1
     (1 行记录)
     
     test=# select proname,provolatile from pg_proc where proname='pg_relpages';
        proname   | provolatile 
     -------------+-------------
      pg_relpages | v
      pg_relpages | v
     (2 行记录)
    

    stable, immutable函数同样的参数多次调用返回结果不变.

    在事务中多次调用返回结果一致的也可归属于stable.

     test=# select now();       
                   now              
     -------------------------------
      2022-11-10 13:42:35.689740+08
     (1 行记录)
     
     test=# select now();
                   now              
     -------------------------------
      2022-11-10 13:42:36.756038+08
     (1 行记录)
     
     test=# begin;
     BEGIN
     test=# select now();
                   now              
     -------------------------------
      2022-11-10 13:42:39.037893+08
     (1 行记录)
     
     test=# select now();
                   now              
     -------------------------------
      2022-11-10 13:42:39.037893+08
     (1 行记录)
     
     test=# select provolatile,proname,proargtypes from pg_proc where proname='now';
      provolatile | proname | proargtypes 
     -------------+---------+-------------
      s           | now     | 
     (1 行记录)
     
     test=# select now();                                                           
                   now              
     -------------------------------
      2022-11-10 13:42:39.037893+08
     (1 行记录)
     
     test=# end;
     COMMIT
     test=# select now();
                   now              
     -------------------------------
      2022-11-10 13:43:31.772061+08
     (1 行记录)
    

    immutable函数同stable, 同样的参数多次调用结果一致.

     test=# select proname,provolatile from pg_proc where proname='abs';
      proname | provolatile 
     ---------+-------------
      abs     | i
      abs     | i
      abs     | i
      abs     | i
      abs     | i
      abs     | i
      abs     | i
     (7 行记录)
     
     test=# select abs(-10);
      abs 
     -----
       10
     (1 行记录)
     
     test=# select abs(-10);
      abs 
     -----
       10
     (1 行记录)
     
     test=# begin;
     BEGIN
     test=# select abs(-10);
      abs 
     -----
       10
     (1 行记录)
     
     test=# select abs(-10);
      abs 
     -----
       10
     (1 行记录)
     
     test=# end;
     COMMIT
     test=# select abs(-10);
      abs 
     -----
       10
     (1 行记录)
    
  2. VOLATILE/STABLE/IMMUTABLE区别:

VOLATILE

	volatile函数不能被优化器作为优化条件. 
		1)例如单SQL处理多行时不能减少volatile函数的调用次数, 
		2)不能使用volatile函数创建函数索引, 
		3)在过滤条件中使用volatile函数时, 不能走索引扫描.
	在同一个查询中, 同样参数的情况下可能被多次执行(QUERY有多行返回/扫描的情况下).

STABLE

	1)优化器可根据实际场景优化stable函数的调用次数, 同样的参数多次调用可能减少成单次调用.
	2)stable函数可以用于优化器选择合适的索引扫描, 因为索引扫描仅评估被比较的值一次, 后多次比较.
	3)stable和volatile函数都不能用于创建函数索引, 只有immutable函数可以用于创建函数索引.

IMMUTABLE

	优化器在处理immutable函数时, 先评估函数结果, 将结果替换为常量.
  1. 理解函数的三态2

     test=# create table t3(id int);
     CREATE TABLE
     test=# insert into t3 values(1),(1),(1),(2),(2),(2); 
     INSERT 0 6
     test=# 
     test=# create or replace function f_t3(id int) returns int as $$ 
     test$# declare
     test$# begin
     test$# raise notice 'Called.';
     test$# return id; 
     test$# end;
     test$# $$ language plpgsql stable; -- stable 状态
     CREATE FUNCTION
     test=# 
     
     
     select * from t3 where f_t3(id) =1; 
     -- f_t3(id) --这里id是变量,不能被优化器优化,因此函数调用了6次.
     test=# select * from t3 where f_t3(id) =1; 
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
      id 
     ----
       1
       1
       1
     (3 行记录)
     
     
     select * from t3 where f_t3(1) =1; 
     -- 这里使用常量调用f_t3()所以可以被优化器优化.
     test=# select * from t3 where f_t3(1) =1; 
     NOTICE:  00000: Called.  -- 函数只被调用一次
     位置:  exec_stmt_raise, pl_exec.c:3908
      id 
     ----
       1
       1
       1
       2
       2
       2
     (6 行记录)
     
     把函数改成volatile后, 函数不能被优化.
     
     test=# alter function f_t3(int) volatile;
     ALTER FUNCTION
     test=# select proname,provolatile from pg_proc where proname='f_t3';
      proname | provolatile 
     ---------+-------------
      f_t3    | v
     (1 行记录)
     
     test=# select * from t3 where f_t3(1) =1;
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
     NOTICE:  00000: Called.
     位置:  exec_stmt_raise, pl_exec.c:3908
      id 
     ----
       1
       1
       1
       2
       2
       2
     (6 行记录)
    

根据函数的实际情况设置稳定态, 可以达到优化效果.

例如f_t3函数调用一次耗时1秒, 并且是stable的状态,以上例子可以减少5秒的查询时间.使用volatile态则需要6秒.

优化器在处理immutable函数时,先评估函数结果,将结果替换为常量.

	test=# explain select * from t2 where id>abs(-1);
                     QUERY PLAN                      
	-----------------------------------------------------
	 Seq Scan on t2  (cost=0.00..41.75 rows=847 width=4)
	   Filter: (id > 1)
	(2 行记录)
	
	因为abs(int)是immutable函数,这里abs(-1)替换成常量1.

如果把函数改成stable, 那么将不会替换成常量.

	test=# alter function abs(int) stable;
	ALTER FUNCTION
	test=# select proname,provolatile from pg_proc where proname='abs'; 
	 proname | provolatile 
	---------+-------------
	 abs     | i
	 abs     | i
	 abs     | i
	 abs     | i
	 abs     | i
	 abs     | s
	 abs     | i
	(7 行记录)
	
	test=# explain select * from t2 where id>abs(-1);                  
	                     QUERY PLAN                      
	-----------------------------------------------------
	 Seq Scan on t2  (cost=0.00..48.10 rows=847 width=4)
	   Filter: (id > abs('-1'::integer))
	(2 行记录)

	由于abs(int)被改成stable了, 将不会替换成常量

只有immutable函数可以创建函数索引.

	test=# create table t4(id int,info timestamp(0));
	CREATE TABLE
	test=# \set VERBOSITY verbose
	test=# create index idx_t4_1 on t4(to_char(info,'yyyymmdd'));
	ERROR:  42P17: functions in index expression must be marked IMMUTABLE
	位置:  ComputeIndexAttrs, indexcmds.c:1940
	
	test=# create table t5(id int primary key, info text);
	CREATE TABLE
	test=# insert into t5 select generate_series(1,100000),md5(random()::text);
	INSERT 0 100000
	
	test=# alter function abs(int) volatile;
	ALTER FUNCTION
	test=# 
	
	test=# \d+ t4
	                                     数据表 "public.t4"
	 栏位 |              类型              | 校对规则 | 可空的 | 预设 | 存储  | 统计目标 | 描述 
	------+--------------------------------+----------+--------+------+-------+----------+------
	 id   | integer                        |          |        |      | plain |          | 
	 info | timestamp(0) without time zone |          |        |      | plain |          | 
	访问方法 heap
	
	test=# \d+ t5
	                            数据表 "public.t5"
	 栏位 |  类型   | 校对规则 |  可空的  | 预设 |   存储   | 统计目标 | 描述 
	------+---------+----------+----------+------+----------+----------+------
	 id   | integer |          | not null |      | plain    |          | 
	 info | text    |          |          |      | extended |          | 
	索引:
	    "t5_pkey" PRIMARY KEY, btree (id)
	访问方法 heap

索引扫描时, 用于过滤条件的表达式只被评估一次后, 再与索引值进行比较判断是否满足条件.

	test=# explain select * from t5 where id<abs(10);
	                        QUERY PLAN                        
	----------------------------------------------------------
	 Seq Scan on t5  (cost=0.00..2341.00 rows=33333 width=37)
	   Filter: (id < abs(10))
	(2 行记录)

只有stable函数和immutable函数符合索引扫描的刚性需求.

	test=# alter function abs(int) stable;
	ALTER FUNCTION
	test=# explain select * from t5 where id<abs(-100);
	                             QUERY PLAN                             
	--------------------------------------------------------------------
	 Index Scan using t5_pkey on t5  (cost=0.29..9.99 rows=97 width=37)
	   Index Cond: (id < abs('-100'::integer))
	(2 行记录)
	
	test=# alter function abs(int) immutable;          
	ALTER FUNCTION
	test=# explain select * from t5 where id<abs(-100);
	                             QUERY PLAN                             
	--------------------------------------------------------------------
	 Index Scan using t5_pkey on t5  (cost=0.29..9.99 rows=97 width=37)
	   Index Cond: (id < 100)
	(2 行记录)



 volatile函数同样的输入参数可能返回不同值,在一个查询中将被多次调用,不符合索引扫描规则.
 stable和immutable同样的参数返回值不变,因此可以作为索引扫描的比较值,优化器允许走索引扫描.
  1. 理解函数的三态3

    函数内的每条查询语句的数据可见性:

    VOLATILE

     snapshot为函数内的每个query开始时的snapshot. 因此对外部已提交的数据时可见的.
    

    STABLE

     snapshot为外部调用函数的QUERY的snapshot, 函数内部始终保持这个snapshot.
    

    IMMUTABLE

     同stable
    

测试:

	test=# create or replace function f_t6() returns void as $$
	test$# declare 
	test$# r record;
	test$# begin
	test$# for i in 1..10 loop
	test$# for r in select * from t6  loop
	test$# raise notice 'loop:%,t6:%.',i,r; 
	test$# end loop; 
	test$# perform pg_sleep(5);
	test$# end loop;
	test$# end;
	test$# $$ language plpgsql volatile;
	CREATE FUNCTION

	test=# create table t6(id int,info text);
	CREATE TABLE

	--执行过程中,新建连接往t6表插入新数据,volatile函数察觉新增行
	test=# select f_t6();
	NOTICE:  loop:2,t6:(1,test).
	NOTICE:  loop:3,t6:(1,test).
	NOTICE:  loop:4,t6:(1,test).
	NOTICE:  loop:5,t6:(1,test).
	NOTICE:  loop:5,t6:(1,test1).
	NOTICE:  loop:6,t6:(1,test).
	NOTICE:  loop:6,t6:(1,test1).
	NOTICE:  loop:6,t6:(1,test3).
	NOTICE:  loop:7,t6:(1,test).
	NOTICE:  loop:7,t6:(1,test1).
	NOTICE:  loop:7,t6:(1,test3).
	NOTICE:  loop:7,t6:(1,test5).
	NOTICE:  loop:8,t6:(1,test).
	NOTICE:  loop:8,t6:(1,test1).
	NOTICE:  loop:8,t6:(1,test3).
	NOTICE:  loop:8,t6:(1,test5).
	NOTICE:  loop:9,t6:(1,test).
	NOTICE:  loop:9,t6:(1,test1).
	NOTICE:  loop:9,t6:(1,test3).
	NOTICE:  loop:9,t6:(1,test5).
	NOTICE:  loop:10,t6:(1,test).
	NOTICE:  loop:10,t6:(1,test1).
	NOTICE:  loop:10,t6:(1,test3).
	NOTICE:  loop:10,t6:(1,test5).
	NOTICE:  loop:10,t6:(1,hello).
	 f_t6 
	------
	 
	(1 行记录)

	test=# insert into t6 values(1,'test');
	INSERT 0 1
	test=# insert into t6 values(1,'test1');
	INSERT 0 1
	test=# insert into t6 values(1,'test3');
	INSERT 0 1
	test=# insert into t6 values(1,'test5');
	INSERT 0 1
	test=# insert into t6 values(1,'hello');
	INSERT 0 1
	test=# 

	--更改稳定态为 stable immutable
	test=# alter function f_t6() stable;
	ALTER FUNCTION
	test=# truncate table t6;
	TRUNCATE TABLE
	test=# select f_t6();               
	 f_t6 
	------
	 
	(1 行记录)
	
	--f_t6()执行过程中对t6变更, 函数不感知.
	test=# insert into t6 values(1,'hello');
	INSERT 0 1
	test=# insert into t6 values(1,'test5');
	INSERT 0 1
	test=# insert into t6 values(1,'test3');
	INSERT 0 1
	test=# insert into t6 values(1,'test3');
	INSERT 0 1
	test=#     
  1. 理解函数的三态4

    STABLE和IMMUTABLE的区别:

     在SELECT子句中,优化器对stable和immutable区别对待.
    

    测试:

     test=# create table t7(id int);
     CREATE TABLE
     test=# insert into t7 values (1),(2),(3);
     INSERT 0 3
     test=# create or replace function f_t7(i int) returns int as $$
     test$# declare
     test$# begin
     test$# raise notice 'called'; 
     test$# return i;
     test$# end;
     test$# $$ language plpgsql stable;
     CREATE FUNCTION
     
     -- SELECT子句中, stable函数不被优化器优化
     test=# select f_t7(1),* from t7;
     NOTICE:  called       --多次调用
     NOTICE:  called
     NOTICE:  called
      f_t7 | id 
     ------+----
         1 |  1
         1 |  2
         1 |  3
     (3 行记录)
     
     
     test=# alter function f_t7(int) immutable;
     ALTER FUNCTION
     
     -- immutable函数被替换成常量, 只执行一次.
     test=# select f_t7(1),* from t7;          
     NOTICE:  called     --一次调用
      f_t7 | id 
     ------+----
         1 |  1
         1 |  2
         1 |  3
     (3 行记录)
    
     --当函数为stable时, 优化器没有处理这种过滤条件, 理论上是可以优化为一次调用f_t7(1)的. 目前仅仅immutable被优化.
     test=# alter function f_t7(int) stable;
     ALTER FUNCTION
     
     
     当使用索引扫描时, stable在这里只会执行一次.
     
     test=# select * from t7 where id=f_t7(1); 
     NOTICE:  called     --有一次为explain的评估输出.     
     NOTICE:  called
     NOTICE:  called
     NOTICE:  called
      id 
     ----
       1
     (1 行记录)
     
     -- 将id=f_t7(1)替换成f_t7(1)=1
     test=# select * from t7 where f_t7(1)=1; 
     NOTICE:  called
      id 
     ----
       1
       2
       3
     (3 行记录)
     
     test=# explain select * from t7 where f_t7(1)=1; 
                              QUERY PLAN                         
     ------------------------------------------------------------
      Result  (cost=0.25..35.65 rows=2540 width=4)
        One-Time Filter: (f_t7(1) = 1)
        ->  Seq Scan on t7  (cost=0.25..35.65 rows=2540 width=4)
     (3 行记录)
     
     test=# alter function f_t7(int) immutable;      
     ALTER FUNCTION
     test=# explain select * from t7 where f_t7(1)=1;
     NOTICE:  called
                           QUERY PLAN                      
     ------------------------------------------------------
      Seq Scan on t7  (cost=0.00..35.40 rows=2540 width=4)
     (1 行记录)
    
  2. 理解函数的三态

    VOLATILE

     1)volatile函数没有限制, 可以修改数据(如执行delete, insert , update).
     2)使用同样的参数调用可能返回不同的值.
     3)volatile函数不能被优化器选择作为优化条件.(例如减少调用, 函数索引, 索引扫描不允许使用volatile函数)
     4)在同一个查询中, 同样参数的情况下可能被多次执行(QUERY有多行返回/扫描的情况下).
     5)snapshot为函数内的每个query开始时的snapshot. 因此对在函数执行过程中, 外部已提交的数据可见.(仅限于调用函数的事务隔离级别为read committed)
    

STABLE

	1)stable和immutable函数, 函数内不允许修改数据.(如PGver>=8.0 函数内不可执行非SELECT|PERFORM语句.)
	2)使用同样的参数调用返回同样的结果, 在事务中有这个特性的也归属stable.
	3)优化器可根据实际场景优化stable函数的调用次数, 同样的参数多次调用可减少成单次调用.
	4)stable和immutable函数可用于优化器选择合适的索引扫描, 因为索引扫描仅评估被比较的表达式一次, 后多次与索引值进行比较.
	5)stable和volatile函数都不能用于创建函数索引, 只有immutable函数可以用于创建函数索引.
	6)stable和immutable函数, snapshot为外部调用函数的QUERY的snapshot, 函数内部始终保持这个snapshot, 外部会话带来的的数据变更不被反映到函数执行过程中.

IMMUTABLE

	1)不允许修改数据, 使用同样的参数调用返回同样的结果. 
	2)优化器在处理immutable函数时, 先评估函数结果, 将结果替换为常量. 
	3)因此使用约束优化查询的场景中也只识别immutable函数.

STABLE和IMMUTABLE的区别

	1)STABLE函数在SELECT和WHERE子句中不被优化, 仅仅当使用索引扫描时WHERE子句对应的STABLE函数才会被优化为1次调用.
	2)在PREPARED STATEMENT中的使用区别:
	3)IMMUTABLE函数在PLAN时以常量替代,STABLE函数在EXECUTE阶段被执行.
	4)因此IMMUTABLE函数参数为常量时,在PREPARED STATEMENT场景只执行一次,而STABLE函数被多次执行.

函数稳定性通过查看PG_PROC/SYS_PROC.PROVOLATILE得到

	PROVOLATILE 说明函数是仅仅只依赖 于它的输入参数 ,还是会被外部 因素影响。
	值 I 表示“不变的”函数,它对于相同的输入总是输出相同的结果。
	值 S 表示“稳定的”函数,它的结果(对于固定输入在一次扫描内不会变化。
	值 V 表示“不稳定的”函数,它的结果在任何时候都可能变化。
	使用V页表示函数具有副作用,所以对它们的调用无法得到优化。
posted @ 2023-03-02 15:50  KINGBASE研究院  阅读(67)  评论(0编辑  收藏  举报