web-dev-qa-db-ja.com

CTEがインラインサブクエリよりもはるかに悪いのはなぜですか

クエリプランナーがpostgresqlでどのように機能するかをよりよく理解しようとしています。

私はこのクエリを持っています:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

私のデータベースでは10ms未満で実行され、usersテーブルには約500kのエントリがあります。

次に、副選択の重複を避けるために、次のようにクエリをCTEとして書き直すことができると考えました。

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

ただし、この書き換えられたクエリは約1秒で実行されます。なぜこれが起こるのですか?説明でジオメトリインデックスを使用していないことを確認しましたが、そのために何ができますか?ありがとう!

クエリを作成する別の方法は次のとおりです。

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

ただし、これはCTEと同じくらい遅くなります。

一方、meパラメーターを抽出して静的に挿入すると、クエリは再び高速になります。

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

最初の(高速)クエリについて説明する

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

2番目の(遅い)クエリの説明

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms
11
viblo

これを試して:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

高速プランを見ると、次のようなものがあります(太字)。

制限(コスト= 5.69..20.11行= 50幅= 36)(実際の時間= 0.512..8.114行= 50ループ= 1)
 InitPlan 1($ 0を返します)
->ユーザーusers_1のusers_pkeyを使用したインデックススキャン(コスト= 0.42..2.64行= 1幅= 32)(実際の時間= 0.032..0.033行= 1ループ= 1)
インデックス条件:(id = 2)
 InitPlan 2($ 1を返します)
-> users users_2のusers_pkeyを使用したインデックススキャン(コスト= 0.42..2.64行= 1幅= 4)(実際の時間= 0.009..0.010行= 1ループ= 1)
インデックスCond:(id = 2)
->ユーザーに対してusers_latest_location_gixを使用したインデックススキャン(コスト= 0.41..70796.51行= 245470幅= 36)(実際の時間= 0.509..8.100行= 50ループ= 1)
並べ替え:(latest_location $ 0)
フィルター:(性別= $ 1)
フィルターによって削除された行数:20 
合計実行時間:8.211 ms 
(12行)

遅いバージョンでは、クエリプランナーはjoinのコンテキストでgenderの等価演算子とlatest_locationのジオメトリ演算子を評価しています。ここで、meの値は各行(正しく推定されているのは1行のみです)。高速バージョンでは、genderlatest_locationの値はscalarsとして扱われます。これらはインラインサブクエリによって発行されるため、処理する値はクエリプランナーにそれぞれ1つしかありません。これは、リテラル値を貼り付けるときに高速プランが得られるのと同じ理由です。

11
Noah Yetter