CTE가 인라인 하위 쿼리보다 훨씬 나쁜 이유


11

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

users 테이블에 약 500k 항목이있는 내 데이터베이스에서 10ms 미만으로 실행됩니다.

그런 다음 중복 하위 선택을 피하기 위해 쿼리를 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)

두 번째 (느린) 쿼리 설명

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

3
나는 최근에 이것에 대해 썼습니다; blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences를 참조하십시오 . 현재 해당 사이트의 도달 가능성을 제한 할 수있는 일부 DNS 문제가 있습니다. FROM최상의 결과를 얻으려면 CTE 용어 대신 하위 쿼리를 사용해보십시오 .
Craig Ringer

(select id, latest_location from users where id = 2)cte로 사용하면 어떻게 되나요? 어쩌면이 문제의 원인이 될 수 있습니다.
cha

나는 당신이 반대 성별의 가장 가까운 사용자를 찾을 것이라고 생각했을 것입니다 :)
cha

@cha cte에서 성별과 위치 만 선택해도 속도에는 차이가 없습니다. (제 경우에는 비슷한 사용자의 평균을 취하고 싶습니다. 질문에 대한 쿼리를 단순화했습니다)
viblo

@CraigRinger 나는 그 최적화 울타리를 생각하지 않습니다. 나는 또한 당신의 제안을 시도했고 그것은 또한 느 렸습니다. 반면에 수동으로 매개 변수를 추출하면 빠릅니다 (필자의 경우 실제 옵션은 최종 결과는 어쨌든 기능입니다).
viblo December

답변:


11

이 시도:

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)
           인덱스 조건 : (id = 2)
   -> 사용자의 users_latest_location_gix를 사용한 인덱스 스캔 (비용 = 0.41..70796.51 행 = 245470 너비 = 36) (실제 시간 = 0.509..8.100 행 = 50 루프 = 1)
         주문자 : (latest_location   $ 0 )
         필터 : (성별 = $ 1 )
         필터로 제거 된 행 : 20
 총 런타임 : 8.211ms
(12 행)

느린 버전에서 쿼리 플래너는 조인 컨텍스트에서 등식 연산자를 gender켜고 지오메트리 연산자를 평가합니다 . 여기서 값은 1 행만 올바르게 추정하더라도 각 행마다 값 이 다를 수 있습니다. 빠른 버전에서 및 의 값은 인라인 서브 쿼리에 의해 생성되므로 스칼라 로 처리됩니다 . 이는 쿼리 플래너에 처리 할 각 값이 하나만 있음을 알려줍니다. 이것이 리터럴 값을 붙여 넣을 때 빠른 계획을 얻는 이유와 동일합니다.latest_locationmegenderlatest_location


나는 지금 당신 mefrom절 에서 제거 할 수 있다고 생각합니다 .
Jarius Hebzo
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.