GROUP BY 및 ORDER BY가있는 큰 테이블에서 느린 쿼리


14

나는 720 만 튜플이있는 테이블을 가지고 있습니다.

                               table public.methods
 column |          type         |                      attributes
--------+-----------------------+----------------------------------------------------
 id     | integer               | not null DEFAULT nextval('methodkey'::regclass)
 hash   | character varying(32) | not null
 string | character varying     | not null
 method | character varying     | not null
 file   | character varying     | not null
 type   | character varying     | not null
Indexes:
    "methods_pkey" PRIMARY KEY, btree (id)
    "methodhash" btree (hash)

이제 일부 값을 선택하고 싶지만 쿼리 속도가 매우 느립니다.

db=# explain 
    select hash, string, count(method) 
    from methods 
    where hash not in 
          (select hash from nostring) 
    group by hash, string 
    order by count(method) desc;
                                            QUERY PLAN
----------------------------------------------------------------------------------------
 Sort  (cost=160245190041.10..160245190962.07 rows=368391 width=182)
   Sort Key: (count(methods.method))
   ->  GroupAggregate  (cost=160245017241.77..160245057764.73 rows=368391 width=182)
       ->  Sort  (cost=160245017241.77..160245026451.53 rows=3683905 width=182)
             Sort Key: methods.hash, methods.string
             ->  Seq Scan on methods  (cost=0.00..160243305942.27 rows=3683905 width=182)
                   Filter: (NOT (SubPlan 1))
                   SubPlan 1
                   ->  Materialize  (cost=0.00..41071.54 rows=970636 width=33)
                     ->  Seq Scan on nostring  (cost=0.00..28634.36 rows=970636 width=33)

hash컬럼의 MD5 해시 인 string및 인덱스를 갖는다. 그래서 내 문제는 전체 테이블이 해시가 아닌 id로 정렬되어 있기 때문에 먼저 정렬 한 다음 그룹화하는 데 시간이 걸립니까?

이 테이블 nostring에는 내가 원하지 않는 해시 목록 만 포함되어 있습니다. 그러나 모든 값을 가지려면 두 테이블이 모두 필요합니다. 따라서 삭제할 수있는 옵션이 아닙니다.

추가 정보 : 열 중 어느 것도 null (테이블 정의에서 수정되지 않음) 일 수 없으며 postgresql 9.2를 사용하고 있습니다.


1
사용하는 PostgreSQL 버전 을 항상 제공하십시오 . NULL열 값 의 백분율은 method얼마입니까? 에 중복이 string있습니까?
Erwin Brandstetter

답변:


18

LEFT JOIN에서 @ dezso의 대답은 잘되어야합니다. 그러나 인덱스는 어쨌든 전체 테이블을 읽어야하기 때문에 유용하지 않습니다 (Postgres 9.2+의 인덱스 전용 스캔 및 유리한 조건은 예외입니다).

SELECT m.hash, m.string, count(m.method) AS method_ct
FROM   methods m
LEFT   JOIN nostring n USING (hash)
WHERE  n.hash IS NULL
GROUP  BY m.hash, m.string 
ORDER  BY count(m.method) DESC;

EXPLAIN ANALYZE쿼리에서 실행하십시오 . 현금 인출 효과와 소음을 배제하기 위해 여러 번. 최상의 결과를 비교하십시오.

쿼리와 일치하는 다중 열 인덱스를 만듭니다.

CREATE INDEX methods_cluster_idx ON methods (hash, string, method);

기다림? 색인이 도움이되지 않는다고 말한 후에? 글쎄, 우리 CLUSTER는 테이블에 필요 합니다.

CLUSTER methods USING methods_cluster_idx;
ANALYZE methods;

다시 실행하십시오 EXPLAIN ANALYZE. 더 빨리? 그것은해야한다.

CLUSTER사용 된 인덱스 순서대로 전체 테이블을 다시 작성하는 일회성 작업입니다. 또한 효과적입니다 VACUUM FULL. 확실하게하려면 VACUUM FULL혼자서 사전 테스트를 실행하여 그 원인이 무엇인지 확인하십시오.

테이블에 많은 쓰기 작업이 표시되면 시간이 지남에 따라 효과가 저하됩니다. CLUSTER근무 외 시간에 일정 을 적용하여 효과를 복원하십시오. 미세 조정은 정확한 사용 사례에 따라 다릅니다. 에 대한 매뉴얼 CLUSTER.

CLUSTER다소 조잡한 도구이며 테이블에 독점 잠금 장치가 필요합니다. 당신이 그것을 감당할 pg_repack수 없다면, 독점 잠금없이 똑같이 할 수 있는 것을 고려 하십시오. 나중에이 답변에 더 많은 것 :


경우 의 비율 NULL열의 값 methodA (~ 20 %의 실제 행 크기에 따라 이상) 높은 부분 인덱스 도와야한다 :

CREATE INDEX methods_foo_idx ON methods (hash, string)
WHERE method IS NOT NULL;

(나중에 업데이트하면 열이로 표시 NOT NULL되므로 적용 할 수 없습니다.)

경우 당신은 PostgreSQL을 실행하는 9.2 또는 그 이상을 (같은 @deszo 댓글을 달았 )없이 제시된 인덱스가 유용 할 수 있습니다 CLUSTER플래너가 활용할 수있는 경우 인덱스 만 스캔 . 유리한 조건에서만 적용 가능 : VACUUM쿼리 의 마지막 열과 모든 열 이후에 가시성 맵에 영향을주는 쓰기 작업이 인덱스에 포함되지 않아야합니다. 기본적으로 읽기 전용 테이블은 언제든지이 테이블을 사용할 수 있지만 많은 테이블은 제한적입니다. Postgres Wiki에 자세한 내용이 있습니다.

이 경우 위에서 언급 한 부분 인덱스가 더 유용 할 수 있습니다.

경우 , 다른 한편으로는,이없는 NULL 열의 값을 method, 당신은해야
1.) 그것은 정의 NOT NULL
2) 사용 count(*)대신에 count(method)약간 빠릅니다와의 부재에서 동일한 작업을 수행, NULL값을.

경우 자주이 쿼리를 호출해야하고 테이블은 읽기 전용하는을 작성 MATERIALIZED VIEW.


이국적인 좋은 점 : 테이블 이름 nostring은이지만 해시가 포함 된 것 같습니다. 문자열 대신 해시를 제외하면 의도 한 것보다 많은 문자열을 제외시킬 수 있습니다. 매우 드물지만 가능합니다.


클러스터가 훨씬 빨라졌습니다. 여전히 검색어에 대해 arround 5min이 필요하지만 밤새 실행하는 것보다 훨씬 낫습니다. : D
reox

@reox : v9.2를 실행 한 이후 : 클러스터링하기 전에 인덱스 만 사용하여 테스트 했습니까? 차이를 본다면 흥미로울 것입니다. (클러스터링 후 차이를 재현 할 수 없습니다.) 또한 EXPLAIN은 인덱스 스캔 또는 전체 테이블 스캔을 표시합니까?
Erwin Brandstetter

5

DBA.SE에 오신 것을 환영합니다!

다음과 같이 쿼리를 바꾸어보십시오.

SELECT m.hash, string, count(method) 
FROM 
    methods m
    LEFT JOIN nostring n ON m.hash = n.hash
WHERE n.hash IS NULL
GROUP BY hash, string 
ORDER BY count(method) DESC;

또는 다른 가능성 :

SELECT m.hash, string, count(method) 
FROM 
    methods m
WHERE NOT EXISTS (SELECT hash FROM nostring WHERE hash = m.hash)
GROUP BY hash, string 
ORDER BY count(method) DESC;

NOT IN 인덱스를 사용하기 어렵 기 때문에 일반적인 성능 싱크입니다.

이것은 인덱스를 통해 더욱 향상 될 수 있습니다. 에 대한 색인 nostring.hash이 유용 해 보입니다. 그러나 먼저 : 지금 무엇을 얻습니까? ( EXPLAIN ANALYZE비용 자체가 작업 시간을 알리지 않기 때문에 결과를 보는 것이 좋습니다 .)


인덱스는 nostring.hash에 이미 생성되었지만 postgres는 너무 많은 튜플 때문에 사용하지 않는다고 생각합니다 ... 시퀀스 스캔을 사용하지 않도록 설정하면 인덱스를 사용합니다. 왼쪽 조인을 사용하면 32 백만의 비용이 발생하므로 더 나은 방법을 원하지만 더 최적화하려고합니다 ...
reox

3
비용은 플래너가 충분히 좋은 계획을 선택할 수있는 비용입니다. 실제 시간은 일반적으로 그와 관련이 있지만 반드시 그런 것은 아닙니다. 따라서 확실하게하려면을 사용하십시오 EXPLAIN ANALYZE.
dezso

1

해시는 md5이므로 숫자로 변환하려고 시도 할 수 있습니다. 숫자로 저장하거나 변경 불가능한 함수에서 해당 숫자를 계산하는 함수형 인덱스를 만들 수 있습니다.

다른 사람들은 이미 md5 값을 텍스트에서 문자열로 변환하는 pl / pgsql 함수를 만들었습니다. 예제는 /programming/9809381/hashing-a-string-to-a-numeric-value-in-postgressql 을 참조 하십시오.

인덱스를 스캔하는 동안 문자열 비교에 많은 시간을 소비하고 있다고 생각합니다. 해당 값을 숫자로 저장하면 실제로 더 빠릅니다.


1
이 전환이 속도를 높일 것이라고 의심합니다. 여기의 모든 쿼리는 비교를 위해 평등을 사용합니다. 숫자 표현을 계산 한 다음 평등을 확인한다고해서 큰 이득을 얻을 수는 없습니다.
dezso

2
나는 오히려 공간 효율성의 수보다 bytea와 같은 MD5를 저장하는 거라고 생각 : sqlfiddle.com/#!12/d41d8/252
잭은 말한다 topanswers.xyz 시도

또한 dba.se에 오신 것을 환영합니다!
잭 topanswers.xyz 시도라고

@ JackDouglas : 재미있는 의견! 32 대신 md5 당 16 바이트는 큰 테이블의 경우 약간입니다.
Erwin Brandstetter

0

나는이 문제를 많이 겪고 간단한 2 부분 트릭을 발견했습니다.

  1. 해시 값에 하위 문자열 인덱스를 만듭니다. (일반적으로 7은 좋은 길이입니다)

    create index methods_idx_hash_substring ON methods(substring(hash,1,7))

  2. 검색 / 조인에 하위 문자열 일치가 포함되도록함으로써 쿼리 플래너가 색인을 사용하도록 힌트를줍니다.

    낡은: WHERE hash = :kwarg

    새로운: WHERE (hash = :kwarg) AND (substring(hash,1,7) = substring(:kwarg,1,7))

raw에 대한 인덱스도 있어야합니다 hash.

결과는 (일반적으로) 플래너가 부분 문자열 색인을 먼저 참조하여 대부분의 행을 제거한다는 것입니다. 그런 다음 전체 32 자 해시를 해당 인덱스 (또는 테이블)에 일치시킵니다. 이 접근법은 800ms 쿼리를 4로 줄였습니다.

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