12

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

入力

EXPLAIN SELECT entityid FROM entity e

LEFT JOIN level1entity l1 ON l1.level1id = e.level1_level1id
LEFT JOIN level2entity l2 ON l2.level2id = l1.level2_level2id
WHERE 

l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f' 
AND 
(entityid NOT IN 
(1377776,1377792,1377793,1377794,1377795,1377796... 50000 ids)
)

出力

Nested Loop  (cost=0.00..1452373.79 rows=3865 width=8)
  ->  Nested Loop  (cost=0.00..8.58 rows=1 width=8)
        Join Filter: (l1.level2_level2id = l2.level2id)
        ->  Seq Scan on level2entity l2  (cost=0.00..3.17 rows=1 width=8)
              Filter: ((userid)::text = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f'::text)
        ->  Seq Scan on level1entity l1  (cost=0.00..4.07 rows=107 width=16)
  ->  Index Scan using fk_fk18edb1cfb2a41235_idx on entity e  (cost=0.00..1452086.09 rows=22329 width=16)
        Index Cond: (level1_level1id = l1.level1id)

ここでは単純化されたバージョンでOKです。結合はボトルネックではありません

SELECT enitityid FROM 
(SELECT enitityid FROM enitity e LIMIT 5000) a

WHERE
(enitityid NOT IN 
(1377776,1377792,1377793,1377794,1377795, ... 50000 ids)
)

問題は、これらの ID を持たないエンティティを見つけることです

説明

Subquery Scan on a  (cost=0.00..312667.76 rows=1 width=8)
  Filter: (e.entityid <> ALL ('{1377776,1377792,1377793,1377794, ... 50000 ids}'::bigint[]))
  ->  Limit  (cost=0.00..111.51 rows=5000 width=8)
        ->  Seq Scan on entity e  (cost=0.00..29015.26 rows=1301026 width=8)
4

4 に答える 4

30

巨大なINリストは非常に非効率的です。理想的には、PostgreSQL はそれを識別し、アンチ結合を実行するリレーションに変換する必要がありますが、この時点では、クエリ プランナーはその方法を知りません。賢明に使用NOT INするので、非常に低コストのチェックでなければなりません。このトピックに関する以前のより詳細な回答を参照してください。

David Aldridge が書いたように、これはアンチ結合に変えることで最もよく解決されます。PostgreSQL はリストをリレーションにVALUES解析するのが非常に高速であるため、リストの結合として記述しますが、効果は同じです。VALUES

SELECT entityid 
FROM entity e
LEFT JOIN level1entity l1 ON l.level1id = e.level1_level1id
LEFT JOIN level2entity l2 ON l2.level2id = l1.level2_level2id
LEFT OUTER JOIN (
    VALUES
    (1377776),(1377792),(1377793),(1377794),(1377795),(1377796)
) ex(ex_entityid) ON (entityid = ex_entityid)
WHERE l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f' 
AND ex_entityid IS NULL; 

COPY値のセットが十分に大きい場合は、一時テーブルを作成し、値をそこに入れ、その上に を作成しPRIMARY KEY、それに結合する方がよい場合もあります。

ここでさらに多くの可能性を探ります:

https://stackoverflow.com/a/17038097/398670

于 2013-07-24T03:38:59.593 に答える
6

ハッシュ アンチ結合を使用するようにクエリを書き直すことができれば、より良い結果が得られる可能性があります。

何かのようなもの:

with exclude_list as (
  select unnest(string_to_array('1377776,1377792,1377793,1377794,1377795, ...',','))::integer entity_id)
select entity_id
from   entity left join exclude_list on entity.entity_id = exclude_list.entity_id
where  exclude_list.entity_id is null;
于 2013-07-23T15:50:48.530 に答える
0

特定のユーザーID「l2.userid =」のwhere句チェックのためにlevel2entityレコードが必要なので、「LEFT JOIN level2entity」を「INNER JOIN level2entity」にする必要があります

INNER JOIN level2entity l2 ON l2.level2id = l1.level2_level2id AND l2.userid = 'a987c246-65e5-48f6-9d2d-a7bcb6284c8f'

これにより、うまくいけば、エンティティがフィルター処理されるため、NOT IN の作業が少なくなります。

于 2013-07-23T14:54:34.080 に答える