web-dev-qa-db-ja.com

Postgresqlクエリプランナーにハッシュ結合でインデックス付きのネストされたループを使用させる

PostgreSQL 9.3.4に読み込まれたStackOverflow-schema関連のデータに問題があります。インデックス付きのネストされたループの代わりにハッシュ結合を使用することを選択しているため、クエリに必要な時間の約10倍の時間がかかります。たとえば、クエリで500人のユーザーを選択した場合、post_tokenizedテーブルでIDとタイプインデックスを使用する代わりに、ハッシュ結合が使用されます。

explain 
select creation_Epoch, user_screen_name, chunk from post_tokenized as tokenized_tbl
                    join posts as posts_tbl
                    on posts_tbl.id = tokenized_tbl.id
                    where type = 'tag'
                    and user_screen_name is not null
                    and owner_user_id in (select id from users where reputation > 100000 order by reputation asc limit 500)
                    and tokenized_tbl.id in (select id from posts where owner_user_id in (select id from users where reputation > 100000 order by reputation asc limit 500))

 Hash Join  (cost=29570.13..751852.55 rows=119954 width=21)
   Hash Cond: (tokenized_tbl.id = posts_tbl.id)
   ->  Index Scan using type_index_post_tokenized on post_tokenized tokenized_tbl  (cost=0.44..646219.29 rows=20281711 width=8)
         Index Cond: (type = 'tag'::text)
   ->  Hash  (cost=29561.73..29561.73 rows=637 width=25)
         ->  Hash Join  (cost=15576.75..29561.73 rows=637 width=25)
               Hash Cond: (posts_tbl.id = posts.id)
               ->  Nested Loop  (cost=48.20..12824.71 rows=106853 width=21)
                     ->  HashAggregate  (cost=47.76..52.76 rows=500 width=4)
                           ->  Limit  (cost=0.43..41.51 rows=500 width=8)
                                 ->  Index Scan using reputation_index_users on users  (cost=0.43..211.57 rows=2570 width=8)
                                       Index Cond: (reputation > 100000)
                     ->  Index Scan using owner_user_id_index_posts on posts posts_tbl  (cost=0.44..23.40 rows=214 width=25)
                           Index Cond: (owner_user_id = users.id)
                           Filter: (user_screen_name IS NOT NULL)
               ->  Hash  (cost=14181.63..14181.63 rows=107754 width=4)
                     ->  HashAggregate  (cost=13104.09..14181.63 rows=107754 width=4)
                           ->  Nested Loop  (cost=48.20..12834.71 rows=107754 width=4)
                                 ->  HashAggregate  (cost=47.76..52.76 rows=500 width=4)
                                       ->  Limit  (cost=0.43..41.51 rows=500 width=8)
                                             ->  Index Scan using reputation_index_users on users users_1  (cost=0.43..211.57 rows=2570 width=8)
                                                   Index Cond: (reputation > 100000)
                                 ->  Index Scan using owner_user_id_index_posts on posts  (cost=0.44..23.40 rows=216 width=8)
                                       Index Cond: (owner_user_id = users_1.id)

しかし、ユーザーの数を200に減らすと、インデックス付きのネストされたループが使用されます(はるかに高速)。

explain 
select creation_Epoch, user_screen_name, chunk from post_tokenized as tokenized_tbl
                    join posts as posts_tbl
                    on posts_tbl.id = tokenized_tbl.id
                    where type = 'tag'
                    and user_screen_name is not null
                    and owner_user_id in (select id from users where reputation > 100000 order by reputation asc limit 200)
                    and tokenized_tbl.id in (select id from posts where owner_user_id in (select id from users where reputation > 100000 order by reputation asc limit 200))

 Nested Loop  (cost=6633.63..466114.15 rows=47982 width=21)
   ->  Hash Join  (cost=6291.07..11836.00 rows=102 width=25)
         Hash Cond: (posts_tbl.id = posts.id)
         ->  Nested Loop  (cost=19.80..5189.72 rows=42741 width=21)
               ->  HashAggregate  (cost=19.36..21.36 rows=200 width=4)
                     ->  Limit  (cost=0.43..16.86 rows=200 width=8)
                           ->  Index Scan using reputation_index_users on users  (cost=0.43..211.57 rows=2570 width=8)
                                 Index Cond: (reputation > 100000)
               ->  Index Scan using owner_user_id_index_posts on posts posts_tbl  (cost=0.44..23.70 rows=214 width=25)
                     Index Cond: (owner_user_id = users.id)
                     Filter: (user_screen_name IS NOT NULL)
         ->  Hash  (cost=5732.50..5732.50 rows=43102 width=4)
               ->  HashAggregate  (cost=5301.48..5732.50 rows=43102 width=4)
                     ->  Nested Loop  (cost=19.80..5193.72 rows=43102 width=4)
                           ->  HashAggregate  (cost=19.36..21.36 rows=200 width=4)
                                 ->  Limit  (cost=0.43..16.86 rows=200 width=8)
                                       ->  Index Scan using reputation_index_users on users users_1  (cost=0.43..211.57 rows=2570 width=8)
                                             Index Cond: (reputation > 100000)
                           ->  Index Scan using owner_user_id_index_posts on posts  (cost=0.44..23.70 rows=216 width=8)
                                 Index Cond: (owner_user_id = users_1.id)
   ->  Bitmap Heap Scan on post_tokenized tokenized_tbl  (cost=342.56..4448.69 rows=502 width=8)
         Recheck Cond: (id = posts_tbl.id)
         Filter: (type = 'tag'::text)
         ->  Bitmap Index Scan on id_index_post_tokenized  (cost=0.00..342.44 rows=43656 width=0)
               Index Cond: (id = posts_tbl.id)

500人のユーザーが選択されているときに、同じプラン(インデックス付きのネストされたループ)を使用するにはどうすればよいですか?次のパラメータを調整してみました:cpu_Tuple_costseq_page_costrandom_page_costeffective_cache_size、( ref )で、プランを変更する方法がわかりません。リクエストされたユーザーの数が増えると計画は変わるようですが、私の環境でのテストから、Postgresが500ユーザーでも同じ計画を維持していれば、はるかに速くなります。

7
Clayton Stanley

SOに関するこの密接に関連する答えは、あなたの主な質問に対する答えを提供するはずです:
単一のSELECTクエリでenable_seqscan = offを設定

現在のトランザクションのハッシュ結合を無効にするために、同様にを使用できます

SET LOCAL enable_hashjoin=off;

しかし、それはではありません私のアドバイスです。あちらで答えを読んでください。
そして、統計とコスト設定についてもこれです。

さらに重要なこと、最初にクエリのもつれをほどく:

SELECT creation_Epoch, user_screen_name, chunk
FROM  (
   SELECT id AS owner_user_id
   FROM   users
   WHERE  reputation > 100000
   ORDER  BY reputation 
   LIMIT  500
   ) u
JOIN   posts p USING (owner_user_id)
JOIN   post_tokenized t USING (id)
WHERE  type = 'tag'
AND    user_screen_name IS NOT NULL;

かなり高速になり、クエリプランナーが最適なプランを選択するのが容易になります(適切なコスト設定とテーブル統計が与えられます)。

11