2017-05-17 14 views
2

私のデータベース設計では、多くの機能が使用されています。そしてそれらの多くは非常に遅いです。だから、実行を少し速くするためにインデックスの一部を作成することは賢明な考えであると判断しました。 しかし、PostgreSQL(9.6)が実際に私のインデックスを使用するように説得することはできません。Postgresは低速機能のインデックスを使用しません

この表「ユーザー」が多い

id integer | name jsonb 
1   | {"last_names": ["Tester"], "first_names": ["Teddy","Eddy"]} 
2   | {"last_names": ["Miller"], "first_names": ["Lisa","Emma"]} 

を考えてみましょう、私は1つの文字列として名前を必要とするが、それは私が置くことを決めた

SELECT array_to_string(jsonb_arr2text_arr(name->'last_names'), ' ') || ', ' || array_to_string(jsonb_arr2text_arr(name->'first_names'), ' '); 

(いわゆる「concat_name」)のようなクエリで行われています複数のテーブルで使用されているため、その機能を関数に組み込むことができます。

CREATE OR REPLACE FUNCTION public.concat_name(name jsonb) 
    RETURNS text AS 
$BODY$ 
    SELECT pg_sleep(50); 
    SELECT array_to_string(jsonb_arr2text_arr(name->'last_names'), ' ') || ', ' || array_to_string(jsonb_arr2text_arr(name->'first_names'), ' '); 
$BODY$ 
    LANGUAGE sql IMMUTABLE SECURITY DEFINER 
    COST 100; 

実際にテストするにはそれが動作するかどうか、私は "人工的に"タイムアウトを追加しました。成功すると(理由pg_sleepの)期待される時間を要する

CREATE INDEX user_concat_name_idx ON "user" (concat_name(name)); 

: は今、私のようなインデックスを作成しました。クエリを実行します。

SELECT concat_name(name) FROM "user"; 

ただし、インデックスが使用されていないため、クエリが非常に遅いです。代わりに、EXPLAINは、プレーナーが「ユーザー」のシーケンススキャンを実行すると伝えます。

私は少しの研究を行いました。多くの人は、テーブルが小さいか、または検索されるデータセットが(ほぼ)テーブル全体であるとクエリプランナーが考えると、シーケンススキャンを行うほうがインデックスを参照してください。 しかし、機能の場合、特に遅いものの場合、それは私には意味をなさない。たとえ1つの行しか含まないテーブルをクエリしても、関数インデックスを使用すると、クエリに毎回50秒かかる関数が含まれていると実行時間が大幅に短縮される可能性があります。

私の意見では、クエリプランナーは、索引付けされた値をルックアップするのに要する時間と、関数を実行するのに要する時間とを比較する必要があります。テーブルまたはクエリ自体(返される行の数)のサイズは、ここでは問題ありません。関数が実行に50秒かかる場合は、インデックスを参照すると常に勝つ必要があります。

だから、毎回関数を実行するのではなく、クエリプレーナがインデックスを使用するようにするにはどうすればよいですか?

答えて

2

最初に、(id, concat_name(name))のインデックスは、concat_name(name)だけを選択したクエリで使用する場合は意味がありません。インデックスは次のとおりです。

create index user_concat_name_idx on "user" (concat_name(name)); 

第2に、インデックスは必要なときに使用されます。あなたがorder by concat_name(name)を追加する場合:

さらに
explain analyse 
select concat_name(name) 
from "user" 
order by 1; 

                    QUERY PLAN                 
----------------------------------------------------------------------------------------------------------------------------------------------- 
Index Scan using user_concat_name_idx on "user" (cost=0.42..29928.42 rows=100000 width=82) (actual time=0.157..1046.168 rows=100000 loops=1) 
Planning time: 0.753 ms 
Execution time: 1048.862 ms 
(3 rows) 

を、あなたの機能をより簡単かつ迅速に行うことができます:

create or replace function concat_name(name jsonb) 
returns text language sql immutable as $$ 
    select concat_ws(', ', 
     (select string_agg(value, ' ') 
     from jsonb_array_elements_text(name->'last_names')), 
     (select string_agg(value, ' ') 
     from jsonb_array_elements_text(name->'first_names')) 
    ) 
$$; 

私は、クエリの平面がインデックスの代わりに、関数を実行を使用するためにここで何を行うことができます新たに毎回?

create or replace function concat_name(name jsonb) 
returns text language sql immutable as $$ 
-- ... 
$$ 
cost 1000; 

the documentationパー:

あなたは機能のより大きなコスト、例えばを宣言する必要があります

execution_cost

関数の推定実行コストを与える正の数cpu_operator_costの単位で指定します。関数が集合を返す場合、これは返される行あたりのコストです。コストが指定されていない場合は、C言語および内部関数の場合は1単位、他のすべての言語の場合は100単位とみなされます。値を大きくすると、プランナは必要以上に機能を評価しないようにします。

+0

最初の点は、発行されたコピー&ペーストであり、動作しなかった実験的なインデックスをコピーしたものです。それを修正しました。 – cis

+0

提案された変更されたconcat_name関数がそのように機能しません。期待される戻り値は「Tester、Teddy Eddy」です。つまりすべての姓+カンマ+すべての姓です。あなたの関数は "Tester、Teddy、Eddy"、すなわちカンマをファーストネームの間に返します。 – cis

+0

あなたはそうです、機能が更新されました。 – klin

関連する問題