순차 스캔 삽입

인덱스 스캔을 기대하는 동안 EXPLAIN 출력에 "순차적 스캔"이 표시됩니까? 이 게시물은 로컬psql에서 실행할 수 있는 SQL을 보여 주는 빠른 둘러보기를 목표로 하는 한 가지 이유에 대해 설명합니다.

이 블로그 게시물의 좋은 후보자라면 ANALYZE가 PostgreSQL의 중요한 기능이라는 것을 알 수 있지만 이유를 말할 수 없거나 중요한 경우에 대해 확신이 서지 않을 수 있습니다.

이에 대해 단계적으로 살펴보겠습니다.
  • 미리 스키마 + 인덱스 설정
  • 데이터에 대한 빠른 쿼리 테스트
  • 감속 생성
  • pg_stats 참조

  • 1. 설정



    이것은 테이블 스키마를 변경하는 유일한 섹션입니다. 이것은 중요합니다. 플래너는 스키마 변경이 아니라 우리의 하루를 망칠 것입니다.

    create table dogs_seen (id serial, num_seen bigint);
    create index dogs_idx on dogs_seen (num_seen);
    
    -- inserts 1mil rows with num_seen values
    -- that are weghted towards low, positive integers
    insert into dogs_seen (num_seen) select (1/random())
      from generate_series(1,1000000);
    
    -- update pg_stats to reflect our table state
    analyze dogs_seen;
    


    2. 초기 테스트



    우리가 집중할 테스트 쿼리는 다음과 같습니다.

    select count(*) from dogs_seen where num_seen = 1;
    -- this should be around 300k-400k
    


    해당 쿼리는 설정 중에 생성한 인덱스를 사용할 것으로 예상하며 다음과 같아야 합니다.

    explain analyze select count(*) from dogs_seen where num_seen = 1;
    
             QUERY PLAN
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Finalize Aggregate  (cost=6320.28..6320.29 rows=1 width=8) (actual time=25.344..26.535 rows=1 loops=1)
       ->  Gather  (cost=6320.07..6320.28 rows=2 width=8) (actual time=25.296..26.527 rows=3 loops=1)
             Workers Planned: 2
             Workers Launched: 2
             ->  Partial Aggregate  (cost=5320.07..5320.08 rows=1 width=8) (actual time=19.418..19.418 rows=1 loops=3)
                   ->  Parallel Index Only Scan using dogs_idx on dogs_seen  (cost=0.42..4973.54 rows=138611 width=0) (actual time=0.062..11.770 rows=111153 loops=3)
                         Index Cond: (num_seen = 1)
                         Heap Fetches: 0
     Planning Time: 0.292 ms
     Execution Time: 26.595 ms
    (10 rows)
    


    예, Index 스캐닝이 표시됩니다. 여기서 주목해야 할 한 가지는 이 쿼리의 성능이 상대적으로 매우 낮다는 것입니다. 설정하는 동안 1에 가중치를 둔 값num_seen을 생성했다고 언급했으므로 조건과 일치하는 행이 이보다 훨씬 더 많습니다.

    explain analyze select count(*) from dogs_seen
      where num_seen = 6;
                                                                   QUERY PLAN
    -----------------------------------------------------------------------------------------------------------------------------------------
     Aggregate  (cost=650.42..650.43 rows=1 width=8) (actual time=5.709..5.710 rows=1 loops=1)
       ->  Index Only Scan using dogs_idx on dogs_seen  (cost=0.42..580.67 rows=27900 width=0) (actual time=0.040..3.502 rows=28235 loops=1)
             Index Cond: (num_seen = 6)
             Heap Fetches: 0
     Planning Time: 0.233 ms
     Execution Time: 5.754 ms
    (6 rows)
    


    힌트로 여기에 비교를 그립니다. 이 쿼리는 플래너에게 얻을 수 있는 최악의 쿼리입니다. 스캔하는 데이터의 양을 줄이기 위해 인덱스를 사용하려고 하지만 수십만 개의 항목을 스캔하도록 만들었습니다.

    플래너에서 상황을 더 나쁘게 만들면 어떻게 될까요? 이 테이블의 더 큰 청크가 쿼리와 일치하도록 만들면 어떻게 될까요?

    3. 둔화 만들기



    다시 말하지만, 순전히 더 많은 행을 삽입하여 속도 저하를 만들 예정이지만 플래너에서 상황을 악화시키고 싶으므로 이러한 행에 다른 가중치를 부여합니다.

    -- insert 1mil more rows, even more weight on low values
    -- (0.5 instead of 1, is the difference)
    insert into dogs_seen (num_seen) select (0.5/random()) from generate_series(1,1000000);
    
    -- don't forget to analyze
    analyze dogs_seen;
    


    이제 원래 25ms SQL에 대해 설명합니다.

    explain analyze select count(*) from dogs_seen where num_seen = 1;
    
                                                                     QUERY PLAN
    ---------------------------------------------------------------------------------------------------------------------------------------------
     Finalize Aggregate  (cost=23271.91..23271.92 rows=1 width=8) (actual time=76.779..77.476 rows=1 loops=1)
       ->  Gather  (cost=23271.69..23271.90 rows=2 width=8) (actual time=76.730..77.471 rows=3 loops=1)
             Workers Planned: 2
             Workers Launched: 2
             ->  Partial Aggregate  (cost=22271.69..22271.70 rows=1 width=8) (actual time=70.811..70.811 rows=1 loops=3)
                   ->  Parallel Seq Scan on dogs_seen  (cost=0.00..21227.67 rows=417611 width=0) (actual time=0.024..57.560 rows=333558 loops=3)
                         Filter: (num_seen = 1)
                         Rows Removed by Filter: 333109
     Planning Time: 0.599 ms
     Execution Time: 77.524 ms
    (10 rows)
    


    동일한 SQL이 순차 스캔으로 변경되었습니다! 내 머릿속에는 PG 기획자가 다음과 같이 말하는 것을 상상하는 대화가 있습니다.
  • 이 SQL에 대한 인덱스의 가치를 낮췄습니다. 우리는 SQL 조건과 일치하는 중복 값이 ​​많은 수많은 행을 삽입했습니다.
  • postgres가 많은 테이블을 스캔해야 한다고 생각하는 경우 인덱스 스캔 작업을 건너뛰고 대신 테이블을 직접 스캔할 수 있습니다.

  • 그러나 postgres는 위의 사항을 어떻게 결정합니까?

    4. pg_stats



    위에서 ANALYZE dogs_seen를 실행했을 때 postgresql은 이 테이블에 대한 pg_stats 행을 업데이트했습니다. 이러한 열은 모두 알고 있으면 유용하며 부담 없이 사용할 수 있습니다read up on them . 여기서 우리가 관심을 갖는 것은 most_common_vals와 그 동료most_common_freqs입니다.

    select left(most_common_vals::text, 10) as most_common_vals,
      left(most_common_freqs::text, 50) as most_common_freqs
    from pg_stats
    where tablename = 'dogs_seen' and attname = 'num_seen';
    
     most_common_vals |                 most_common_freqs
    ------------------+----------------------------------------------------
     {1,2,3,4,5       | {0.5011333,0.19793333,0.08653333,0.047066666,0.029
    (1 row)
    


    이것은 다음과 같이 읽습니다.
  • 1이 샘플링된 행
  • 의 50%에 표시됩니다.
  • 2이 샘플링된 행
  • 의 20%에서 나타납니다.

  • 동일한 값을 가진 많은 행을 삽입함으로써 ANALYZE가 목록을 채울 때 이 목록의 항목에 영향을 미쳤습니다. 그리고 플래너는 인덱스를 사용하는 대신 값1을 순차적으로 스캔하도록 조정했습니다.

    이 집으로 운전하기 위해 많은 1 를 삭제하고 다시 살펴보겠습니다.

    -- delete 500k rows, make "1" great again
    delete from dogs_seen where id in (select id from dogs_seen where num_seen = 1 limit 500000);
    
    -- re-analyze
    analyze dogs_seen;
    
    -- check out the new stats
    select left(most_common_vals::text, 10) as most_common_vals, left(most_common_freqs::text, 50) as most_common_freqs from pg_stats where tablename = 'dogs_seen' and attname = 'num_seen';
    
     most_common_vals |                 most_common_freqs
    ------------------+----------------------------------------------------
     {1,2,3,4,5       | {0.32923332,0.27103335,0.11643333,0.0607,0.0405666
    (1 row)
    
    -- did we switch it back onto the index?
    explain analyze select count(*) from dogs_seen where num_seen = 1;
                                                                                QUERY PLAN
    ------------------------------------------------------------------------------------------------------------------------------------------------------------------
     Finalize Aggregate  (cost=9520.65..9520.66 rows=1 width=8) (actual time=33.478..34.535 rows=1 loops=1)
       ->  Gather  (cost=9520.44..9520.65 rows=2 width=8) (actual time=33.384..34.527 rows=3 loops=1)
             Workers Planned: 2
             Workers Launched: 2
             ->  Partial Aggregate  (cost=8520.44..8520.45 rows=1 width=8) (actual time=28.603..28.603 rows=1 loops=3)
                   ->  Parallel Index Only Scan using dogs_idx on dogs_seen  (cost=0.43..8006.01 rows=205771 width=0) (actual time=0.265..17.126 rows=166891 loops=3)
                         Index Cond: (num_seen = 1)
                         Heap Fetches: 0
     Planning Time: 0.417 ms
     Execution Time: 34.570 ms
    (10 rows)
    

    Index 스캔으로 돌아왔습니다! 당신은 그것을 가지고 있습니다. ANALYZE를 실행하여 행을 삽입하고 쿼리 계획을 변경했습니다. 당신을 괴롭히는 순차 스캔이 있다면 아마도 이것 때문일 것입니다!

    좋은 웹페이지 즐겨찾기