postgres에서 정렬 속도를 높이기 위해 인덱스를 사용하는 방법


10

postgres 9.4를 사용하고 있습니다.

messages스키마는 다음과 같습니다. 메시지는 feed_id에 속하고 posts_at이며, 메시지는 부모 메시지를 가질 수 있습니다 (응답의 경우).

                    Table "public.messages"
            Column            |            Type             | Modifiers
------------------------------+-----------------------------+-----------
 message_id                   | character varying(255)      | not null
 feed_id                      | integer                     |
 parent_id                    | character varying(255)      |
 posted_at                    | timestamp without time zone |
 share_count                  | integer                     |
Indexes:
    "messages_pkey" PRIMARY KEY, btree (message_id)
    "index_messages_on_feed_id_posted_at" btree (feed_id, posted_at DESC NULLS LAST)

에 의해 주문 된 모든 메시지를 반환하고 share_count싶지만 각각에 대해 parent_id하나의 메시지 만 반환하고 싶습니다. 즉, 여러 메시지가 동일한 parent_id경우 최신 메시지 ( posted_at) 만 반환됩니다. 은 parent_idnull 일 수 있으며 null이있는 메시지 parent_id는 모두 반환되어야합니다.

내가 사용한 쿼리는 다음과 같습니다.

WITH filtered_messages AS (SELECT * 
                           FROM messages
                           WHERE feed_id IN (7) 
                           AND (posted_at >= '2015-01-01 04:00:00.000000') 
                           AND (posted_at < '2015-04-28 04:00:00.000000'))
    SELECT *
    FROM (SELECT DISTINCT ON(COALESCE(parent_id, message_id)) parent_id,
                          message_id, 
                          posted_at, 
                          share_count
          FROM filtered_messages
          ORDER BY COALESCE(parent_id, message_id), posted_at DESC NULLS LAST
         ) messages
    ORDER BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;

다음은 http://sqlfiddle.com/#!15/588e5/1/0 입니다. SQL Fiddle에서 스키마, 정확한 쿼리 및 예상 결과를 정의했습니다.

그러나 메시지 테이블이 커지면 쿼리 성능이 느려집니다. 여러 정렬 색인을 추가하려고 시도했지만 색인을 사용하지 않는 것 같습니다. 설명은 다음과 같습니다. http://explain.depesz.com/s/Sv2

올바른 색인을 만들려면 어떻게해야합니까?


언뜻보기에 ORDER BY하위 쿼리에서는 완전히 쓸모가 없습니다. 또한 연결된 계획은 게시 된 쿼리의 결과 일 수 없습니다 metadata. 예를 들어에 대한 언급은 없습니다 .
dezso

귀하의 설명의 역할을 포함하지 않습니다 feed_idposted_at당신은 언급하지 않았다 metadataJSON 유형을 것으로 보인다 전혀? 일관성을 유지하기 위해 질문을 복구하십시오. CTE에서> 500k 개 행을 선택합니다. 테이블에 몇 개의 행이 있습니까? CTE에서 일반적으로 몇 퍼센트의 행을 선택합니까? 행의 몇 퍼센트가 parent_id IS NULL있습니까? 성능 문제 에 대해서는 [postgresql-performance] 태그 의 정보를 고려하십시오 .
Erwin Brandstetter

또한 중요 : 각 행 수는 몇 개 parent_id입니까? (최소 / 평균 / 최대)
Erwin Brandstetter

죄송합니다, 일부 열을 줄임으로써 질문을 더 명확하게하려고 노력했습니다 metadata. share_count는 실제로 hstore에있었습니다 . 현재 메시지 테이블의 데이터는 10mil이지만 빠르게 증가합니다. 각 feed_id에 대해 파티션 테이블로 분리한다고 생각합니다. 피드 ID 당만 가져 오기 때문입니다. parent_id null의 비율과 null이 아닌 비율은 약 60 % / 40 %입니다. 일반적인 페치는 테이블의 1-2 % 정도입니다. (약 100K 메시지) 100K의 성능은 약 1 초이지만 한 번 500K +에 도달하면 비트 맵 인덱스를 사용하며 보통 10 초가 걸립니다.
Zhaohan Weng

답변:


9

질문

이 쿼리는 어떤 경우에도 상당히 빨라야합니다.

SELECT parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NULL  -- match index condition
UNION ALL
(
SELECT DISTINCT ON(parent_id)
       parent_id, message_id, posted_at, share_count
FROM   messages
WHERE  feed_id = 7
AND    posted_at >= '2015-01-01 4:0:0'
AND    posted_at <  '2015-04-28 4:0:0'
AND    parent_id IS NOT NULL  -- match index condition
ORDER  BY parent_id, posted_at DESC NULLS LAST
)
ORDER  BY share_count DESC NULLS LAST, posted_at DESC NULLS LAST;
  • CTE는 일반 하위 쿼리도 제공 할 수없는 작업을 수행하지 않습니다. 그리고 CTE는 별도로 실행되고 결과가 구체화되기 때문에 최적화 장벽을 도입합니다.

  • 실제로 필요한 것보다 하나 이상의 하위 쿼리 수준이 있습니다.

  • 식이 (COALESCE(parent_id, message_id)일반 인덱스와 호환되지 않으므로 해당 식에 대한 인덱스가 필요합니다. 그러나 데이터 배포에 따라 그다지 유용하지 않을 수 있습니다. 자세한 내용은 아래 링크를 참조하십시오.

  • 간단한 경우를 parent_id IS NULL별도로 SELECT나누면 최적의 결과를 얻거나 얻지 못할 수 있습니다. 특히, 드문 경우이지만 인덱스가있는 결합 된 쿼리가 (COALESCE(parent_id, message_id)더 잘 수행 될 수 있습니다. 다른 고려 사항이 적용됩니다 ...

지수

특히 다음 지수로 지원되는 경우 :

CREATE INDEX messages_idx_null ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NULL;

CREATE INDEX messages_idx_notnull ON messages (
  feed_id
, posted_at DESC NULLS LAST
, share_count DESC NULLS LAST
, parent_id, message_id
)
WHERE parent_id IS NOT NULL;

두 개의 부분 인덱스는 전체 테이블을 함께 포함 하며 단일 총 인덱스와 크기가 거의 같습니다.

마지막 두 열 parent_id, message_id인덱스 전용 스캔 을 얻는 경우에만 의미가 있습니다. . 그렇지 않으면 두 지수 모두에서 제거하십시오.

SQL 바이올린.

누락 된 세부 사항에 따라 DISTINCT ON목적에 가장 적합한 쿼리 기술 일 수도 있고 아닐 수도 있습니다. 자세한 설명을 읽으십시오.

그리고 여기에 더 빠른 대안이 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.