web-dev-qa-db-ja.com

postgresにvarchar []でGINインデックスを使用させる

Postgres 9.4データベースでは、各行の識別子のリストを含むフィールドidentifiers: varchar(512)[]のテーブルがあります。新しい行を追加するときは、そのidentifiersリストが既存の行と重複しないようにしたいので、その列のGINインデックスを使用して、このような競合する行を最初に検索します。


    Column      |           Type           |                        Modifiers                         
----------------+--------------------------+-----------------------------------------------------------
id              | integer                  | not null default nextval('my_table_id_seq'::regclass)    
identifiers     | character varying(512)[] | 
... other columns ...

Indexes:
    "my_table_pkey" PRIMARY KEY, btree (id)
    "gin_idx_identifiers" gin (identifiers) WITH (fastupdate='on')
    ... other indexes ...

問題は、デフォルトで、次のクエリがGINインデックスを使用しないことです。

EXPLAIN ANALYZE
SELECT * FROM "my_table"
WHERE "my_table"."identifiers" && ARRAY['a_sample_identifier']::varchar(512)[] LIMIT 1;

                                                            QUERY PLAN                                                            
----------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.00..20.97 rows=1 width=1368) (actual time=31120.985..31120.986 rows=1 loops=1)
   ->  Seq Scan on my_table  (cost=0.00..4318413.15 rows=205920 width=1368) (actual time=31120.983..31120.983 rows=1 loops=1)
         Filter: (identifiers && '{a_sample_identifier}'::character varying(512)[])
         Rows Removed by Filter: 14572058
 Planning time: 0.135 ms
 Execution time: 31121.023 ms

enable_seqscan = off、次にインデックスが使用されます(そして、クエリは驚くほど速くなります):

                                                                QUERY PLAN                                                                
------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=1755.88..1757.62 rows=1 width=1368) (actual time=0.065..0.065 rows=1 loops=1)
   ->  Bitmap Heap Scan on my_table  (cost=1755.88..359284.85 rows=205920 width=1368) (actual time=0.064..0.064 rows=1 loops=1)
         Recheck Cond: (identifiers && '{a_sample_identifier}'::character varying(512)[])
         Heap Blocks: exact=1
         ->  Bitmap Index Scan on gin_idx_identifiers  (cost=0.00..1704.40 rows=205920 width=0) (actual time=0.056..0.056 rows=1 loops=1)
               Index Cond: (identifiers && '{a_sample_identifier}'::character varying(512)[])
 Planning time: 0.126 ms
 Execution time: 0.102 ms
(8 rows)

ただし、その設定を読みましたenable_seqscan = offグローバルに危険です。 postgresにインデックスを強制的に使用させるにはどうすればよいですか?

2
pintoch

EXCLUDE制約を使用して、システムに自動的にチェックを行わせることができます。配置すると、制約に違反する行を挿入しようとすると、自動的にエラーがスローされます。これはおそらくあなたの最良のオプションです。最初に手動でチェックしてから行を挿入すると、競合する2つの行を正常に挿入できるようになる可能性があります。

何らかの理由で実際にチェックを行う必要がある場合は、クエリを書き直して、LIMIT 1ではなくCOUNT(*)を使用し、カウントがゼロかどうかを確認します。

問題は、seqスキャンが一致する1つの行を非常に迅速に検出し、停止する可能性があることです。 COUNT(*)を使用してクエリを書き込むと、プランナはseqスキャンの真のコストを実現するように強制されます。または、クエリからLIMIT 1を削除するだけで、クライアント側で1行のみをフェッチし、それ以上フェッチしないようにすることもできます。

1
jjanes