标簽
PostgreSQL , 三态 , stable , 調用次數
https://github.com/digoal/blog/blob/master/201806/20180629_02.md#%E8%83%8C%E6%99%AF 背景
immutable 函數,輸入參數靜态時,傳回結果不變,是以它在plan前先計算,得到一個常量後,放到QUERY裡面再生成執行計劃。
stable 函數,輸入參數靜态時,如果在一個事務中多次調用它,傳回結果不變。
顯然一個immutable 函數在同一個SQL中,永遠隻調用一次,(在bind , execute的模式下則隻有prepare的時候被調用一次)
但是一個stable函數,雖然輸入參數靜态時,如果在一個事務中多次調用它,傳回結果不變。但是它可能被多次調用。
例如在同一個QUERY中,如果要掃描多條記錄時,stable函數可能被多次調用。
1、全表掃描,掃描多少條記錄,就需要調用多少次。(同時對于有靜态參數輸入或無參數的stable函數,需要再加一次執行計劃時的評估)
2、索引掃描:
需要區分STABLE函數輸入的是靜态還是動态參數(或volatile函數的傳回值作為參數)。
靜态參數,索引掃描時隻需要調用兩次(不管掃描多少索引條目),一次是執行計劃,一次是索引掃描前計算stable函數的傳回值,然後就是索引掃了。
如果stable函數輸入的是動态參數,不支援索引。是以隻可能走全表。
https://github.com/digoal/blog/blob/master/201806/20180629_02.md#%E4%BE%8B%E5%AD%90 例子
1、建立一個stable函數,裡面通過raise來輸出資訊,判斷這個函數被調用了多少次。
無輸入參數
postgres=# create or replace function ff() returns int as $$
declare
begin
raise notice 'a';
return 1;
end;
$$ language plpgsql strict stable;
CREATE FUNCTION
2、建立測試表
postgres=# create table test123(id int);
CREATE TABLE
postgres=# insert into test123 values (1),(2),(3);
INSERT 0 3
3、生成執行計劃時,stable函數被執行一次(指無動态參數的stable函數,有動态參數的,不執行)
postgres=# explain select * from test123 where id=ff();
NOTICE: a
QUERY PLAN
----------------------------------------------------------
Seq Scan on test123 (cost=0.00..679.38 rows=13 width=4)
Filter: (id = ff())
(2 rows)
4、使用全表掃描,有多少記錄就會被執行多少次,同時加上一次執行計劃的評估,一共調用了4次
postgres=# select * from test123 where id=ff();
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
id
----
1
(1 row)
5、建立索引
postgres=# create index idx_test123 on test123(id);
CREATE INDEX
6、強制使用索引掃描
postgres=# set enable_seqscan=off;
SET
7、生成執行計劃,調用一次
postgres=# explain select * from test123 where id=ff();
NOTICE: a
QUERY PLAN
--------------------------------------------------------------------------------
Index Only Scan using idx_test123 on test123 (cost=0.38..2.60 rows=1 width=4)
Index Cond: (id = ff())
(2 rows)
8、執行+執行計劃,分别調用一次
postgres=# select * from test123 where id=ff();
NOTICE: a 第一次生成執行計劃
NOTICE: a 第二次是索引掃描前的函數值計算
id
----
1
(1 row)
9、新增一條資料
postgres=# insert into test123 values (1);
INSERT 0 1
10、符合條件的資料有2條,不影響索引掃描時,無參數或無動态參數的stable函數的調用次數
postgres=# select * from test123 where id=ff();
NOTICE: a
NOTICE: a
id
----
1
1
(2 rows)
postgres=# select * from test123 where id>ff();
NOTICE: a
NOTICE: a
id
----
2
3
(2 rows)
postgres=# select * from test123 where id>=ff();
NOTICE: a
NOTICE: a
id
----
1
1
2
3
(4 rows)
postgres=# explain analyze select * from test123 where id>ff();
NOTICE: a
NOTICE: a
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Index Scan using idx_test123 on test123 (cost=0.38..2.60 rows=1 width=4) (actual time=0.067..0.070 rows=2 loops=1)
Index Cond: (id > ff())
Planning time: 0.185 ms
Execution time: 0.127 ms
(4 rows)
11、建立一個帶參數的stable函數
create or replace function ff(int) returns int as $$
declare
begin
raise notice 'a';
return 1;
end;
$$ language plpgsql strict stable;
12、插入10條重複值
postgres=# insert into test123 select 1 from generate_series(1,10);
INSERT 0 10
13、使用靜态參數輸入時,可以走索引,是以依舊隻調用2次。
postgres=# select * from test123 where id>ff(1);
NOTICE: a 執行計劃
NOTICE: a 索引掃描前固定stable函數值
id
----
2
3
(2 rows)
postgres=# explain select * from test123 where id>ff(1);
NOTICE: a
QUERY PLAN
---------------------------------------------------------------------------
Index Scan using idx_test123 on test123 (cost=0.38..2.60 rows=1 width=4)
Index Cond: (id > ff(1))
(2 rows)
14、改成動态參數,那麼就不能走索引掃描,調用次數等于記錄數,因為explain時也不會被調用
調用了14次。
postgres=# explain analyze select * from test123 where id>ff(id);
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
QUERY PLAN
---------------------------------------------------------------------------------------------------------------------
Seq Scan on test123 (cost=10000000000.00..10000000001.79 rows=1 width=4) (actual time=0.085..0.159 rows=2 loops=1)
Filter: (id > ff(id))
Rows Removed by Filter: 12
Planning time: 0.085 ms
Execution time: 0.197 ms
(5 rows)
postgres=# select * from test123 where id>ff(id);
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
NOTICE: a
id
----
2
3
(2 rows)
postgres=# select count(*) from test123;
count
-------
14
(1 row)