Postgres가 인덱스 스캔 대신 순차적 스캔을 수행 중입니다.


9

약 1000 만 개의 행이 있고 날짜 필드에 색인이있는 테이블이 있습니다. 인덱싱 된 필드의 고유 값을 추출하려고하면 결과 집합에 26 개의 항목 만 있어도 Postgres가 순차적 스캔을 실행합니다. 옵티마이 저가 왜이 계획을 선택합니까? 그리고 어떻게 피할 수 있습니까?

다른 답변에서 나는 이것이 인덱스와 관련된 쿼리와 관련이 있다고 생각합니다.

explain select "labelDate" from pages group by "labelDate";
                              QUERY PLAN
-----------------------------------------------------------------------
 HashAggregate  (cost=524616.78..524617.04 rows=26 width=4)
   Group Key: "labelDate"
   ->  Seq Scan on pages  (cost=0.00..499082.42 rows=10213742 width=4)
(3 rows)

테이블 구조 :

http=# \d pages
                                       Table "public.pages"
     Column      |          Type          |        Modifiers
-----------------+------------------------+----------------------------------
 pageid          | integer                | not null default nextval('...
 createDate      | integer                | not null
 archive         | character varying(16)  | not null
 label           | character varying(32)  | not null
 wptid           | character varying(64)  | not null
 wptrun          | integer                | not null
 url             | text                   |
 urlShort        | character varying(255) |
 startedDateTime | integer                |
 renderStart     | integer                |
 onContentLoaded | integer                |
 onLoad          | integer                |
 PageSpeed       | integer                |
 rank            | integer                |
 reqTotal        | integer                | not null
 reqHTML         | integer                | not null
 reqJS           | integer                | not null
 reqCSS          | integer                | not null
 reqImg          | integer                | not null
 reqFlash        | integer                | not null
 reqJSON         | integer                | not null
 reqOther        | integer                | not null
 bytesTotal      | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesHTML       | integer                | not null
 bytesJS         | integer                | not null
 bytesCSS        | integer                | not null
 bytesImg        | integer                | not null
 bytesFlash      | integer                | not null
 bytesJSON       | integer                | not null
 bytesOther      | integer                | not null
 numDomains      | integer                | not null
 labelDate       | date                   |
 TTFB            | integer                |
 reqGIF          | smallint               | not null
 reqJPG          | smallint               | not null
 reqPNG          | smallint               | not null
 reqFont         | smallint               | not null
 bytesGIF        | integer                | not null
 bytesJPG        | integer                | not null
 bytesPNG        | integer                | not null
 bytesFont       | integer                | not null
 maxageMore      | smallint               | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 maxage365       | smallint               | not null
 maxage30        | smallint               | not null
 maxage1         | smallint               | not null
 maxage0         | smallint               | not null
 maxageNull      | smallint               | not null
 numDomElements  | integer                | not null
 numCompressed   | smallint               | not null
 numHTTPS        | smallint               | not null
 numGlibs        | smallint               | not null
 numErrors       | smallint               | not null
 numRedirects    | smallint               | not null
 maxDomainReqs   | smallint               | not null
 bytesHTMLDoc    | integer                | not null
 fullyLoaded     | integer                |
 cdn             | character varying(64)  |
 SpeedIndex      | integer                |
 visualComplete  | integer                |
 gzipTotal       | integer                | not null
 gzipSavings     | integer                | not null
 siteid          | numeric                |
Indexes:
    "pages_pkey" PRIMARY KEY, btree (pageid)
    "pages_date_url" UNIQUE CONSTRAINT, btree ("urlShort", "labelDate")
    "idx_pages_cdn" btree (cdn)
    "idx_pages_labeldate" btree ("labelDate") CLUSTER
    "idx_pages_urlshort" btree ("urlShort")
Triggers:
    pages_label_date BEFORE INSERT OR UPDATE ON pages
      FOR EACH ROW EXECUTE PROCEDURE fix_label_date()

답변:


8

이것은 Postgres 최적화와 관련하여 알려진 문제입니다. 고유 한 값이 귀하의 경우와 같이 적고 8.4+ 버전 인 경우 재귀 쿼리를 사용하는 매우 빠른 해결 방법은 다음과 같습니다. Loose Indexscan .

쿼리를 다시 작성할 수 있습니다 ( LATERAL9.3 이상 버전 필요).

WITH RECURSIVE pa AS 
( ( SELECT labelDate FROM pages ORDER BY labelDate LIMIT 1 ) 
  UNION ALL
    SELECT n.labelDate 
    FROM pa AS p
         , LATERAL 
              ( SELECT labelDate 
                FROM pages 
                WHERE labelDate > p.labelDate 
                ORDER BY labelDate 
                LIMIT 1
              ) AS n
) 
SELECT labelDate 
FROM pa ;

Erwin Brandstetter는이 답변에서 다양한 관련 쿼리에 대한 자세한 설명과 몇 가지 변형을 제공합니다 (관련되지만 다른 문제). GROUP BY 쿼리를 최적화하여 사용자 당 최신 레코드를 검색합니다.


6

가장 좋은 쿼리는 데이터 배포에 달려 있습니다 .

날짜 당 많은 행 이 있으며 설정되었습니다. 결과가 26 개의 값으로 만 소실되므로 다음 솔루션은 모두 색인을 사용하자마자 엄청나게 빠릅니다.
(더 뚜렷한 값을 원하면 더 흥미로울 것입니다.)

포함 할 필요가 없다 pageid 전혀 (당신이 주석 등).

인덱스

에 필요한 간단한 btree 인덱스 만 있으면됩니다 "labelDate".
열에 NULL 값이 몇 개 이상인 경우 부분 인덱스 가 더 도움이되고 더 작습니다.

CREATE INDEX pages_labeldate_nonull_idx ON big ("labelDate")
WHERE  "labelDate" IS NOT NULL;

나중에 설명했다 :

0 % NULL이지만 임포트 할 때 수정 한 후에 만 ​​가능합니다.

부분 인덱스 여전히 NULL 값을 가진 행의 중간 상태를 배제하는 것이 합리적 일 수 있습니다. 불필요하게 인덱스가 업데이트되는 것을 피할 수 있습니다.

질문

임시 범위를 기반으로

날짜가 너무 좁지 않은 연속 범위에 나타나는 경우 데이터 유형의 특성을 date활용할 수 있습니다. 주어진 두 값 사이에는 유한하고 셀 수있는 값 수가 있습니다. 간격이 적 으면 가장 빠릅니다.

SELECT d."labelDate"
FROM  (
   SELECT generate_series(min("labelDate")::timestamp
                        , max("labelDate")::timestamp
                        , interval '1 day')::date AS "labelDate"
   FROM   pages
   ) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

왜에 캐스트 timestamp에서 generate_series()? 보다:

최소값과 최대 값을 지수에서 저렴하게 선택할 수 있습니다. 가능한 최소 및 / 또는 최대 날짜 를 알고 있으면 조금 더 저렴 해집니다. 예:

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM   generate_series(0, now()::date - date '2011-01-01' - 1) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

또는 불변 간격 동안 :

SELECT d."labelDate"
FROM  (SELECT date '2011-01-01' + g AS "labelDate"
       FROM generate_series(0, 363) g) d
WHERE  EXISTS (SELECT FROM pages WHERE "labelDate" = d."labelDate");

느슨한 인덱스 스캔

이것은 날짜 당 많은 행이있는 한 모든 날짜 분포에서 매우 잘 수행됩니다. 기본적으로 @ypercube가 이미 제공 한 것 . 그러나 몇 가지 좋은 점이 있으므로 좋아하는 인덱스를 어디에서나 사용할 수 있어야합니다.

WITH RECURSIVE p AS (
   ( -- parentheses required for LIMIT
   SELECT "labelDate"
   FROM   pages
   WHERE  "labelDate" IS NOT NULL
   ORDER  BY "labelDate"
   LIMIT  1
   ) 
   UNION ALL
   SELECT (SELECT "labelDate" 
           FROM   pages 
           WHERE  "labelDate" > p."labelDate" 
           ORDER  BY "labelDate" 
           LIMIT  1)
   FROM   p
   WHERE  "labelDate" IS NOT NULL
   ) 
SELECT "labelDate" 
FROM   p
WHERE  "labelDate" IS NOT NULL;
  • 첫 번째 CTE p는 사실상

    SELECT min("labelDate") FROM pages

    그러나 자세한 형식은 부분 인덱스가 사용되도록합니다. 또한이 양식은 일반적으로 내 경험과 테스트에서 약간 더 빠릅니다.

  • 단일 열의 경우 rCTE의 재귀 용어에서 상관 된 하위 쿼리가 조금 더 빨라야합니다. "labelDate"에 대해 NULL이되는 행을 제외해야합니다. 보다:

  • 사용자 별 최신 레코드를 검색하도록 GROUP BY 쿼리 최적화

옆으로

따옴표가없는 합법적 인 소문자 식별자는 삶을 더 편하게 만듭니다.
일부 디스크 공간을 절약하기 위해 테이블 ​​정의에서 열을 우선적으로 정렬하십시오.


-2

postgresql 문서에서 :

CLUSTER는 지정된 인덱스에서 인덱스 스캔을 사용 하거나 (인덱스가 b- 트리 인 경우) 순차적 스캔을 사용하여 테이블을 다시 정렬 할 수 있습니다 . 플래너 비용 매개 변수 및 사용 가능한 통계 정보를 기반으로 더 빠른 방법을 선택하려고 시도합니다.

labelDate의 색인은 btree입니다.

참고:

http://www.postgresql.org/docs/9.1/static/sql-cluster.html


'2000-01-01'과 '2020-01-01'사이에 'WHERE "labelDate"와 같은 조건이 있더라도 여전히 순차적 스캔이 필요합니다.
Charlie Clark

현재 클러스터링 (데이터가 대략 순서대로 입력되었지만) 여전히 WHERE 절에서도 인덱스를 사용하지 않기로 한 쿼리 플래너 결정을 설명하지는 않습니다.
Charlie Clark

세션에 대한 순차 스캔을 사용 안함으로 설정하려고 했습니까? set enable_seqscan=off어쨌든 문서는 명확합니다. 클러스터링하면 순차적 스캔이 수행됩니다.
Fabrizio Mazzoni 2016 년

예, 순차 스캔을 비활성화하려고 시도했지만 별 차이가 없었습니다. 이 쿼리의 속도는 실제 쿼리에서 JOINS에 사용할 수있는 조회 테이블을 만드는 데 사용하므로 실제로 중요하지 않습니다.
Charlie Clark
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.