하위 쿼리가 있는 pg_hint_plan

이 시리즈의 이전 게시물에서 본 것처럼 테이블 별칭으로 힌트를 정의합니다. 하지만 별칭이 없다면 어떻게 될까요? 내부 이름이 될 수 있는 이름에 대한 실행 계획을 살펴보십시오.

예시:

drop table demo2, demo1;
create table demo1 ( id bigint primary key, a int, b int, d int);
create table demo2 ( id bigint primary key, a int, b int, d int);
insert into demo1 select generate_series(1,1000), 0, 0, 0 ;
vacuum analyze demo1;
vacuum analyze demo2;
insert into demo1 select generate_series(100000,200000), 0, 0, 0 ;


이제 테이블demo1에서 1000개의 행을 가져와 demo2로 이동하는 다음 쿼리가 있습니다.

explain (costs off, analyze)
WITH del AS(
 DELETE FROM demo1 a
 WHERE a.id in (SELECT id from demo1 my_batch limit 1000)
 RETURNING *
)  INSERT INTO demo2 
   SELECT  id, a, b c
 FROM del;


실행 계획은 다음과 같습니다.

                                               QUERY PLAN

--------------------------------------------------------------------------------------------------------
 Insert on demo2 (actual time=18.627..18.635 rows=0 loops=1)
   CTE del
     ->  Delete on demo1 a (actual time=0.415..17.051 rows=1000 loops=1)
           ->  Hash Semi Join (actual time=0.399..16.534 rows=1000 loops=1)
                 Hash Cond: (a.id = "ANY_subquery".id)
                 ->  Seq Scan on demo1 a (actual time=0.006..10.032 rows=101001 loops=1)
                 ->  Hash (actual time=0.355..0.360 rows=1000 loops=1)
                       Buckets: 1024  Batches: 1  Memory Usage: 79kB
                       ->  Subquery Scan on "ANY_subquery" (actual time=0.006..0.256 rows=1000 loops=1)
                             ->  Limit (actual time=0.003..0.131 rows=1000 loops=1)
                                   ->  Seq Scan on demo1 my_batch (actual time=0.002..0.089 rows=1000 loops=1)
   ->  CTE Scan on del (actual time=0.417..17.282 rows=1000 loops=1)
 Planning Time: 0.247 ms
 Execution Time: 18.702 ms
(14 rows)


postgres=#


이것은 demo1가 해시 조인을 위한 프로브 테이블로 먼저 전체 스캔되기 때문에 그다지 효율적이지 않습니다. 기본적으로 PostgreSQL은 삭제하려는 1000개id를 해시했지만 전체 테이블을 읽어서 찾습니다. 통계가 정확하지 않다고 생각할 수 있습니다. ANALYZE를 실행해 보십시오. 좋아, 하지만 내 목표는 많은 행을 삭제하는 것입니다(1000개 단위로). 전과 후를 분석하고 싶지 않습니다. 이것은 pg_hint_plan가 도움이 되는 곳입니다. 내 쿼리(LIMIT 1000 포함) 설계에 따라 예측 가능한 성능(테이블 크기가 아닌 처리된 행 수에 따른 시간)을 얻기 위해 해당 행에서 중첩 루프가 필요합니다.

조인이 삭제할 테이블인 demo1 a 와 1000demo1 my_batchid 사이에 있다고 생각할 수 있지만 /*+ Leading( (my_batch a) ) NestLoop(my_batch a) */ 와 같은 힌트를 얻으려고 하면 성공하지 못할 것입니다.

실행 계획을 보십시오. 해시 조인은 Seq Scan on demo1 aSubquery Scan on "ANY_subquery" 두 스캔 사이에 있습니다. 이것은 조인 순서, 방향 및 방법에 사용해야 하는 별칭을 제공합니다: a"ANY_subquery"
중첩 루프를 강제로 실행하려는 경우 힌트NestLoop("ANY_subquery" a)를 추가할 수 있지만 이는 두 테이블 간의 조인이 중첩 루프 조인이어야 한다는 의미일 뿐입니다. 이 힌트에서 별칭의 순서는 중요하지 않습니다. 조인 쌍이 있는 Leading hind가 필요합니다: Leading( ("ANY_subquery" a) )
explain (costs off, analyze)
/*+ Leading( ("ANY_subquery" a) ) NestLoop("ANY_subquery" a) */
WITH del AS(
 DELETE FROM demo1 a
 WHERE a.id in (SELECT id from demo1 my_batch limit 1000)
 RETURNING *
)  INSERT INTO demo2 
   SELECT  id, a, b c
 FROM del;


이것이 내가 원하는 계획입니다.

                                                  QUERY PLAN

---------------------------------------------------------------------------------------------------------------
 Insert on demo2 (actual time=4.267..4.268 rows=0 loops=1)
   CTE del
     ->  Delete on demo1 a (actual time=0.521..2.219 rows=1000 loops=1)
           ->  Nested Loop (actual time=0.508..1.701 rows=1000 loops=1)
                 ->  HashAggregate (actual time=0.492..0.641 rows=1000 loops=1)
                       Group Key: "ANY_subquery".id
                       Batches: 1  Memory Usage: 193kB
                       ->  Subquery Scan on "ANY_subquery" (actual time=0.069..0.305 rows=1000 loops=1)
                             ->  Limit (actual time=0.065..0.177 rows=1000 loops=1)
                                   ->  Seq Scan on demo1 my_batch (actual time=0.064..0.134 rows=1000 loops=1)
                 ->  Index Scan using demo1_pkey on demo1 a (actual time=0.001..0.001 rows=1 loops=1000)
                       Index Cond: (id = "ANY_subquery".id)
   ->  CTE Scan on del (actual time=0.523..2.526 rows=1000 loops=1)
 Planning Time: 0.231 ms
 Execution Time: 4.362 ms
(15 rows)

4.362 ms vs. 18.702 ms 가 빨라서 행복하다고 생각하세요? 전혀. 나는 그 밀리 초에 대해 신경 쓰지 않습니다. 이 계획을 선호하는 이유는 확장성입니다. 여기서는 소스 테이블의 크기에 따라 달라지는 작업이 없습니다. 내 힌트 덕분에 오래된 통계로 인해 O(N) 없음으로 전환할 위험이 없는 O(1) 복잡성을 보장합니다.

이 예제는 PostgreSQL(pg_hint_plan 확장 포함) 또는 호환 가능(pg_hint_plan이 기본적으로 설치된 YugabyteDB와 같은)에서 실행됩니다.

좋은 웹페이지 즐겨찾기