web-dev-qa-db-ja.com

DISTINCTクエリを高速化する方法はありますか?

データベースにテーブルtがあります(PostgreSQL 10.4):

\d t;
                Table "public.t"
  Column  |          Type          | Collation | Nullable | Default 
----------+------------------------+-----------+----------+---------
 sn       | character varying(11)  |           |          | 
 site     | character varying(50)  |           |          | 
Indexes:
    "site_2018_idx" btree (site), tablespace "indexspace"
    "sn_2018_idx" btree (sn), tablespace "indexspace"

特定のサイトの明確な「sn」を見つける必要があります。これは次のように行います。

SELECT DISTINCT sn FROM t WHERE site='a_b301_1' ORDER BY sn ;

機能しますが、非常に遅く、75の異なる「sn」値を返すには約8分かかります。それをスピードアップする方法はありますか? Explain分析はこの出力を提供します:

QUERY PLAN                                                                                 
----------------------------------------------------------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=42873094.21..42873103.25 rows=3615 width=12) (actual time=190431.413..190431.417 rows=75 loops=1)
   Output: sn
   Sort Key: t.sn
   Sort Method: quicksort  Memory: 28kB
   ->  HashAggregate  (cost=42872844.42..42872880.57 rows=3615 width=12) (actual time=190431.233..190431.263 rows=75 loops=1)
         Output: sn
         Group Key: t.sn
         ->  Bitmap Heap Scan on public.t  (cost=874850.36..42695793.24 rows=70820471 width=12) (actual time=8755.163..168773.143 rows=43096912 loops=1)
               Output: sn, site
               Recheck Cond: ((t.site)::text = 'a_b301_1'::text)
               Heap Blocks: exact=783666
               ->  Bitmap Index Scan on site_2018_idx  (cost=0.00..857145.24 rows=70820471 width=0) (actual time=8540.835..8540.835 rows=43096912 loops=1)
                     Index Cond: ((t.site)::text = 'a_b301_1'::text)
 Planning time: 0.466 ms
 Execution time: 190433.289 ms
(15 rows)

追加情報

推奨されたように別のインデックスを作成した後(site, sn)、時間が8分から30秒に大幅に減少しました。それは素晴らしいです、なぜこれが事実であるのか理解できません。この場合、1つのマルチカラムインデックスは2つの個別のインデックスよりも優れていますか?

2
lugger1

提案されているように、(site, sn)は、特にテーブルが十分にバキュームされていて、インデックスのみのスキャンの恩恵を受ける場合に、これを高速化できます。

それでもスピードアップが不十分な場合は、このインデックスを「スキップスキャン」または 「ルーズインデックススキャン」 で使用する方法があります。個別の値の数がインデックスのサイズよりはるかに少ないクエリを高速化します。残念ながらPostgreSQLのプランナーはこの機会を自動的に検出しませんが、再帰的な共通テーブル式を作成することにより、自分で強制することができます。この手法の説明は PostgreSQL wiki で見つけることができますが、パラメーター化されたバージョンに適応させる必要があります。

結果のクエリは非常に醜いので、ビューにラップすることをお勧めします。または、この場合、パラメーター化されているため、セットを返す関数に入れます。このようなものが私にとってはうまくいきます:

CREATE FUNCTION public.foobar(text) RETURNS SETOF text
    LANGUAGE sql
    AS $_$ with recursive r as (
  (select sn from t where site = $1 order by sn limit 1)
   union all
  SELECT (SELECT t.sn FROM t WHERE site=$1 and t.sn > r.sn ORDER BY sn LIMIT 1) from r where sn is not null
)
select * from r where r.sn is not null $_$;

テーブルとインデックスの設定:

create table t as select 
    floor(random()*1.2)::int::varchar as site, 
    (-floor(50*log(random()))::int)::varchar as sn 
from generate_series(1,10000000);

create index on t (site ,sn);
4
jjanes