web-dev-qa-db-ja.com

フィルターに大きな(?)INリストを含むPostgresql 10クエリ

私は比較的単純なクエリに取り組んでいます:

SELECT row.id, row.name FROM things AS row 
WHERE row.type IN ( 
'00000000-0000-0000-0000-000000031201',
...
 )
ORDER BY row.name ASC, row.id ASC 
LIMIT 2000;

問題:

リストに含まれるUUIDが25以下の場合、クエリは問題ありません。

 Limit  (cost=21530.51..21760.51 rows=2000 width=55) (actual time=5.057..7.780 rows=806 loops=1)
   ->  Gather Merge  (cost=21530.51..36388.05 rows=129196 width=55) (actual time=5.055..6.751 rows=806 loops=1)
         Workers Planned: 1
         Workers Launched: 1
         ->  Sort  (cost=20530.50..20853.49 rows=129196 width=55) (actual time=2.273..2.546 rows=403 loops=2)
               Sort Key: name, id
               Sort Method: quicksort  Memory: 119kB
               ->  Parallel Index Only Scan using idx_things_type_name_id on things row  (cost=0.69..9562.28 rows=129196 width=55) (actual time=0.065..0.840 rows=403 loops=2)
                     Index Cond: (type = ANY ('{00000000-0000-0000-0000-000000031201,... (< 24 more)}'::text[]))
                     Heap Fetches: 0
 Planning time: 0.202 ms
 Execution time: 8.485 ms

しかし、リストが25要素より大きくなると、別のインデックスが使用され、クエリの実行時間が実際に長くなります。

 Limit  (cost=1000.58..15740.63 rows=2000 width=55) (actual time=11.553..29789.670 rows=952 loops=1)
   ->  Gather Merge  (cost=1000.58..2400621.01 rows=325592 width=55) (actual time=11.551..29855.053 rows=952 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Parallel Index Scan using idx_things_name_id on things row  (cost=0.56..2362039.59 rows=135663 width=55) (actual time=3.570..24437.039 rows=317 loops=3)
               Filter: ((type)::text = ANY ('{00000000-0000-0000-0000-000000031201,... (> 24 more)}'::text[]))
               Rows Removed by Filter: 5478258
 Planning time: 0.209 ms
 Execution time: 29857.454 ms

詳細:

テーブルには16435726行、17列が含まれています。クエリに関連する3つの列は次のとおりです。

  • id-varchar(36)、null以外、一意、主キー
  • タイプ-varchar(36)、外部キー
  • 名前-varchar(2000)

関連するインデックスは次のとおりです。

  • モノ(id)に一意のインデックスidx_things_pkeyを作成します。
  • 事物(タイプ)にインデックスidx_things_typeを作成します。
  • 物(名前、ID)にインデックスidx_things_name_idを作成します。
  • 事物(タイプ、名前、ID)にインデックスidx_things_type_name_idを作成します。

70の異なる型の値があり、そのうち2つは約1500万行を占めます。これら2つはINリストにありません。

実験と質問:

このインデックスが役立つかどうかを確認することから始めました:

インデックスidx_things_name_id_typeを作成します(名前、ID、タイプ)。

ほんの少しでした。 12秒は受け入れられません。

 Limit  (cost=1000.71..7638.73 rows=2000 width=55) (actual time=5.888..12120.907 rows=952 loops=1)
   ->  Gather Merge  (cost=1000.71..963238.21 rows=289917 width=55) (actual time=5.886..12154.580 rows=952 loops=1)
         Workers Planned: 2
         Workers Launched: 2
         ->  Parallel Index Only Scan using idx_things_name_id_type on things row  (cost=0.69..928774.57 rows=120799 width=55) (actual time=1.024..9852.923 rows=317 loops=3)
               Filter: ((type)::text = ANY ('{00000000-0000-0000-0000-000000031201,... 37 more}'::text[]))
               Rows Removed by Filter: 5478258
               Heap Fetches: 0
 Planning time: 0.638 ms
 Execution time: 12156.817 ms

大きなINリストはPostgresで効率的ではないことを知っていますが、25要素になるとすぐにこれにヒットすることに驚きました。または、ここでの問題は別のものですか?

他の投稿で提案されている解決策を試しました(VALUESリストへの内部結合、INもIN VALUESに変更するなど)が、事態はさらに悪化しました。実験の場合の例を次に示します。

SELECT row.id, row.name
FROM things AS row 
WHERE row.type IN (VALUES ('00000000-0000-0000-0000-000000031201'), ... )
ORDER BY row.name ASC, row.id ASC 
LIMIT 2000;
 Limit  (cost=0.56..1254.91 rows=2000 width=55) (actual time=45.718..847919.632 rows=952 loops=1)
   ->  Nested Loop Semi Join  (cost=0.56..10298994.72 rows=16421232 width=55) (actual time=45.714..847917.788 rows=952 loops=1)
         Join Filter: ((row.type)::text = "*VALUES*".column1)
         Rows Removed by Join Filter: 542360414
         ->  Index Scan using idx_things_name_id on things row  (cost=0.56..2170484.38 rows=16421232 width=92) (actual time=0.132..61387.582 rows=16435726 loops=1)
         ->  Materialize  (cost=0.00..0.58 rows=33 width=32) (actual time=0.001..0.022 rows=33 loops=16435726)
               ->  Values Scan on "*VALUES*"  (cost=0.00..0.41 rows=33 width=32) (actual time=0.004..0.030 rows=33 loops=1)
 Planning time: 1.131 ms
 Execution time: 847920.680 ms
(9 rows)

内部結合値からのクエリプラン():

 Limit  (cost=0.56..1254.91 rows=2000 width=55) (actual time=38.289..847714.160 rows=952 loops=1)
   ->  Nested Loop  (cost=0.56..10298994.72 rows=16421232 width=55) (actual time=38.287..847712.333 rows=952 loops=1)
         Join Filter: ((row.type)::text = "*VALUES*".column1)
         Rows Removed by Join Filter: 542378006
         ->  Index Scan using idx_things_name_id on things row  (cost=0.56..2170484.38 rows=16421232 width=92) (actual time=0.019..60303.676 rows=16435726 loops=1)
         ->  Materialize  (cost=0.00..0.58 rows=33 width=32) (actual time=0.001..0.022 rows=33 loops=16435726)
               ->  Values Scan on "*VALUES*"  (cost=0.00..0.41 rows=33 width=32) (actual time=0.002..0.029 rows=33 loops=1)
 Planning time: 0.247 ms
 Execution time: 847715.215 ms
(9 rows)

ここで何か間違ったことをしていますか?

これを処理する方法に関するヒントはありますか?さらに情報が必要な場合は、皆さんの質問に応じて追加します。

ps。列/テーブル/インデックス名は、会社のポリシーに準拠するために「匿名化」されているため、愚かな名前を指さないでください:)

4
Dominik Boszko

70の異なる型の値があり、そのうち2つは約1500万行を占めます。これら2つはINリストにありません

そして、36文字のキーを使用して、このdomainに対処します。これは36 * 8ビットのスペースで、7ビットしか必要ありません。

  • これらの70の異なるタイプを、代理キーを含む別個のLookUpTableに配置します
  • 元のtypeフィールドは、このテーブルでUNIQUE制約を持つことができます
  • サロゲートIDを外部キーとして使用して、このテーブルを参照します
  • ...および:他のobeseキーについてもおそらく同じ
0
wildplasser

これは、PostgreSQLがサブクエリJOINを使用するかどうかを選択する方法に関連しています。

クエリオプティマイザーは、いくつかの内部ルールと統計的な動作により、(インデックスを使用するかどうかにかかわらず)行動する方法を自由に決定し、最も安価なコストで実行しようとします。

しかし、この決定が最善ではない場合もあります。 join_collapse_limitを変更する結合を使用するようにオプティマイザを「強制」できます。

SET join_collapse_limit = 1を使用してから、クエリを実行してインデックスを「強制」することができます。

このオプションをセッションに配置することを忘れないでください。通常、クエリオプティマイザーは決定に権利を持っています。

サブクエリの場合に機能したオプションです。IN内で試してみると、クエリでインデックスを強制的に使用するのにも役立ちます。