PostgreSQLの関数属性の基本を
振り返る
2017.12.16
PostgreSQLアンカンファレンス
笠原 辰仁 @kasa_zip
はじめに
● PostgreSQLでは組み込みの関数やユーザ定義関数を使用
できます
– sum(), to_char(), now(), substring() ...
● 各関数には属性が付与されています
– IMMUTABLE, STABLE, VOLATILE の3つ
– 何も指定しないとVOLATILEとして作成される
● 普段何気なく使っている関数の属性が、どのように作用す
るかを把握しておくと、性能を引き出すとか、不可思議な
挙動の理解が容易になります。
PostgreSQLの関数属性について
● IMMUTABLE, STABLE, VOLATILE の3つの属性について
– 詳細はマニュアルに記載されています。
属性 DBへの変更 関数の返却値 変動性
IMMUTABLE しない ある引数に対して常に同じ結果を返す。 低
STABLE しない ある引数に対して同一クエリ内であれば
同じ結果を返す
中
VOLATILE する ある引数に対して実行のたびに異なる結
果を返す
高
IMMUTABLE関数例:upper / lower / floor / ceil
STABLE関数例:to_char / to_timestamp / array_to_string
VOLATILE関数例:random / clock_timestamp / nextval
その他、pg_procシステムビューで詳しくみれます。
[参考] 初期DBの属性別の関数の数
Immutableな関数が多いが大多数はint2in/int2out/int2eqなどの
型別の入出力制御や比較演算用のもの
=# select provolatile, count(*) from pg_proc group by provolatile order by 1;
provolatile | count
-------------+-------
i | 2031
s | 568
v | 295
変動の属性について
● 変動性はクエリの実行時の最適化を図るために必要になり
ます
– とりわけ、関数実行時にVOLATILEとそれ以外では顕著
な差が出ます
属性 最適化に関する特長
IMMUTABLE 同一引数に対する呼び出しには常に同一の結果を返すことが保
証されています。 問い合わせが定数の引数でこうした関数を呼
び出した場合、オプティマイザはこの関数を事前に評価するこ
とができます。
STABLE 単一の文内ですべての行に対して同一の引数を渡した場合に同
一の結果を返すことが保証されています。
VOLATILE 同一引数で続けて呼び出したとしても異なる結果を返すことが
できます。
関数の属性を意識するところ
● あるあるな状況(というか他に無い?)
– 関数インデックスを作成する時
test=# create index t1_idx_ok on t1 (upper(c2));
CREATE INDEX
test=# create index t1_idx_ng on t1 (to_timestamp(c2, 'YYYY-MM-DD'));
ERROR: functions in index expression must be marked IMMUTABLE
[おまけ]なぜ IMMUTABLEではなくSTABLE?
● to_char()などはIMMUTABLEでも良さそうなのですが?
– 外部要因で出力結果が左右されることがあるためです
– 例えばTimezoneやlocaleなど
postgres=# set lc_time TO 'ja_JP';
SET
postgres=# select to_char(now(), 'TMDay');
to_char
---------
土曜日
(1 row)
postgres=# set lc_time TO 'de_DE';
SET
postgres=# select to_char(now(), 'TMDay');
to_char
---------
Samstag
(1 row)
関数の属性を意識するところ
● 実際にあまり気にするものではありませんが、数点ほ
ど、気にしておくことで性能上有利になるかもしれま
せん。
例) 定数呼び出し時の差
CREATE OR REPLACE FUNCTION get_val_im(i int) RETURNS int AS
$$
DECLARE
ret int;
BEGIN
SELECT val INTO ret FROM my_val WHERE c1 = i;
RETURN ret;
END;
$$
LANGUAGE plpgsql IMMUTABLE;
上記と同じ内容をSTABLE、VOLATILEの属性指定で作成
例) 定数呼び出し時の差
postgres=# explain analyze select get_val_im(1) FROM
generate_series(1,100000);
QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..10.00 rows=1000 width=4) (actual time=19.542..38.856 rows=100000 loops=1)
Planning time: 1.109 ms
Execution time: 53.288 ms
(3 rows)
postgres=# explain analyze select get_val_st(1) FROM generate_series(1,100000);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..260.00 rows=1000 width=4) (actual time=19.855..1130.991 rows=100000 loops=1)
Planning time: 0.044 ms
Execution time: 1150.938 ms
(3 rows)
postgres=# explain analyze select get_val_vo(1) FROM generate_series(1,100000);
QUERY PLAN
----------------------------------------------------------------------------------------------------------------------------
Function Scan on generate_series (cost=0.00..260.00 rows=1000 width=4) (actual time=20.324..1194.374 rows=100000 loops=1)
Planning time: 0.043 ms
Execution time: 1212.163 ms
(3 rows)
例) 定数呼び出し時の差
postgres=# create table tbl (c1 int primary key, c2 text);
postgres=# insert into tbl select generate_series(1,20000) , 'D';
postgres=# explain analyze select * FROM tbl WHERE c1 = get_val_im(1);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Scan using tbl_pkey on tbl (cost=0.29..8.30 rows=1 width=6) (actual time=0.020..0.022
rows=1 loops=1)
Index Cond: (c1 = 100)
Planning time: 0.245 ms
Execution time: 0.045 ms
(4 rows)
postgres=# explain analyze select * FROM tbl WHERE c1 = get_val_st(1);
QUERY PLAN
--------------------------------------------------------------------------------------------------------------
Index Scan using tbl_pkey on tbl (cost=0.54..8.55 rows=1 width=6) (actual time=0.055..0.057
rows=1 loops=1)
Index Cond: (c1 = get_val_st(1))
Planning time: 0.208 ms
Execution time: 0.085 ms
(4 rows)
postgres=# explain analyze select * FROM tbl WHERE c1 = get_val_vo(1);
QUERY PLAN
--------------------------------------------------------------------------------------------------
Seq Scan on tbl (cost=0.00..5339.00 rows=1 width=6) (actual time=2.790..242.450 rows=1
loops=1)
Filter: (c1 = get_val_vo(1))
Rows Removed by Filter: 19999
Planning time: 0.092 ms
Execution time: 242.477 ms
(5 rows)

PostgreSQLの関数属性を知ろう