하위 쿼리 추가시 PostgreSQL 쿼리 속도가 매우 느림


10

1.5M 행이있는 테이블에서 비교적 간단한 쿼리가 있습니다.

SELECT mtid FROM publication
WHERE mtid IN (9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE 산출:

Limit  (cost=8.84..12.86 rows=1 width=8) (actual time=0.985..0.986 rows=1 loops=1)
  ->  Bitmap Heap Scan on publication  (cost=8.84..12.86 rows=1 width=8) (actual time=0.984..0.985 rows=1 loops=1)
        Recheck Cond: ((mtid = 9762715) OR (last_modifier = 21321))
        ->  BitmapOr  (cost=8.84..8.84 rows=1 width=0) (actual time=0.971..0.971 rows=0 loops=1)
              ->  Bitmap Index Scan on publication_pkey  (cost=0.00..4.42 rows=1 width=0) (actual time=0.295..0.295 rows=1 loops=1)
                    Index Cond: (mtid = 9762715)
              ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..4.42 rows=1 width=0) (actual time=0.674..0.674 rows=0 loops=1)
                    Index Cond: (last_modifier = 21321)
Total runtime: 1.027 ms

지금까지는 좋고 빠르며 사용 가능한 색인을 사용합니다.
이제 쿼리를 약간 수정하면 결과는 다음과 같습니다.

SELECT mtid FROM publication
WHERE mtid IN (SELECT 9762715) OR last_modifier=21321
LIMIT 5000;

EXPLAIN ANALYZE출력은 다음과 같습니다

Limit  (cost=0.01..2347.74 rows=5000 width=8) (actual time=2735.891..2841.398 rows=1 loops=1)
  ->  Seq Scan on publication  (cost=0.01..349652.84 rows=744661 width=8) (actual time=2735.888..2841.393 rows=1 loops=1)
        Filter: ((hashed SubPlan 1) OR (last_modifier = 21321))
        SubPlan 1
          ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.001 rows=1 loops=1)
Total runtime: 2841.442 ms

너무 빠르지 않고 seq 스캔을 사용하는 중 ...

물론 응용 프로그램이 실행하는 원래 쿼리는 조금 더 복잡하고 느리며 최대 절전 모드로 생성 된 원본은 그렇지 (SELECT 9762715)않지만 속도는 느려집니다 (SELECT 9762715)! 쿼리는 최대 절전 모드에서 생성되므로 변경하기가 상당히 어려우며 일부 기능을 사용할 수 없습니다 (예 : UNION사용할 수 없으므로 빠름).

질문

  1. 두 번째 경우에 색인을 사용할 수없는 이유는 무엇입니까? 그들은 어떻게 사용될 수 있습니까?
  2. 다른 방법으로 쿼리 성능을 향상시킬 수 있습니까?

추가 생각

SELECT를 수동으로 수행 한 다음 결과 목록을 쿼리에 넣어 첫 번째 경우를 사용할 수 있습니다. IN () 목록에 5000 개의 숫자가 있어도 두 번째 솔루션보다 4 배 빠릅니다. 그러나 그것은 잘못 된 것처럼 보입니다 (또한 100 배 빠를 수도 있습니다 :)). 쿼리 플래너가이 두 쿼리에 대해 완전히 다른 방법을 사용하는 이유를 완전히 이해할 수 없으므로이 문제에 대한 더 좋은 해결책을 찾고 싶습니다.


어떻게하면 최대 절전 모드 JOIN에서 IN ()? 대신에 최대 절전 모드가 생성되도록 코드를 다시 작성할 수 있습니까 ? 또한 publication최근에 분석 되었습니까?
dezso

예, VACUUM ANALYZE와 VACUUM FULL을 모두 수행했습니다. 성능에는 변화가 없었습니다. 두 번째로 AFAIR는이를 시도했지만 쿼리 성능에 큰 영향을 미치지 않았습니다.
P.Peter

1
Hibernate가 적절한 쿼리를 생성하지 못한다면, 왜 원시 SQL을 사용하지 않습니까? 영어로 번역하는 방법을 이미 잘 알고있는 동안 Google 번역을 요구하는 것과 같습니다. 귀하의 질문에 관해서 : 그것은 실제로 숨겨진 숨겨진 쿼리에 달려 (SELECT 9762715)있습니다.
Erwin Brandstetter

아래에서 언급했듯이 내부 쿼리 가 인 경우에도 속도가 느립니다 (SELECT 9762715). 최대 절전 모드 질문 : 그것은 가능하지만 사용자가 정의한 최대 절전 모드 기준 쿼리가 즉석에서 번역되므로 심각한 코드 재 작성이 필요합니다. 따라서 본질적으로 우리는 가능한 많은 부작용을 가진 거대한 사업 인 최대 절전 모드를 수정합니다.
P.Peter

답변:


6

문제의 핵심은 여기서 명백해집니다.

게시 시퀀 스 스캔 (비용 = 0.01..349652.84 행 = 744661 너비 = 8) (실제 시간 = 2735.888..2841.393 행 = 1 루프 = 1)

Postgres 744661 개의 행을 반환하지만 실제로는 단일 행으로 판명됩니다. Postgres가 쿼리에서 무엇을 기대해야하는지 잘 모르면 더 잘 계획 할 수 없습니다. 실제 쿼리는 숨겨져 (SELECT 9762715)있어야하며 테이블 정의, 제한 조건, 카디널리티 및 데이터 분배도 알고 있어야합니다. 분명히, Postgres는 얼마나 적은 행이 반환 되는지 예측할 수 없습니다 . 이 내용에 따라 쿼리를 다시 작성하는 방법이있을 수 있다 .

하위 쿼리가 행을 초과하여 반환 할 수 없다는 것을 알고 있다면 다음 n을 사용하여 Postgres에 알릴 수 있습니다.

SELECT mtid
FROM   publication
WHERE  mtid IN (SELECT ... LIMIT n) --  OR last_modifier=21321
LIMIT  5000;

경우 n이 충분히 작은이며, 포스트 그레스 (비트 맵) 인덱스 스캔으로 전환됩니다. 그러나 간단한 경우에만 작동합니다. OR조건을 추가 할 때 작업이 중지됩니다 . 쿼리 플래너는 현재 그에 대처할 수 없습니다.

나는 거의 처음 IN (SELECT ...)부터 사용하지 않습니다 . 일반적으로 EXISTSsemi-join을 사용하여 동일한 방법을 구현하는 더 좋은 방법이 있습니다. 때로는 ( LEFT) JOIN( LATERAL) ...

확실한 해결책은을 사용하는 UNION것이지만, 당신은 그것을 배제했습니다. 실제 하위 쿼리 및 기타 관련 세부 정보를 모른 채 더 이상 말할 수 없습니다.


2
숨겨진 쿼리없습니다(SELECT 9762715) ! 위에서 본 정확한 쿼리를 실행하면 물론 원래의 최대 절전 모드 쿼리는 조금 더 복잡하지만 쿼리 플래너가 타락하는 위치를 정확하게 파악할 수 있었기 때문에 쿼리의 해당 부분을 제시했습니다. 그러나 위의 설명과 쿼리는 ctrl-cv입니다.
P.Peter

두 번째 부분은 내부 한계가 작동하지 않습니다. EXPLAIN ANALYZE SELECT mtid FROM publication WHERE mtid IN (SELECT 9762715 LIMIT 1) OR last_modifier=21321 LIMIT 5000;순차적 스캔도 수행하고 약 3 초 동안 실행됩니다.
P.Péter

@ P.Péter : Postgres 9.4의 실제 하위 쿼리를 사용하여 로컬 테스트에서 작동합니다. 표시하는 것이 실제 쿼리 인 경우 솔루션이 이미있는 것입니다. 질문의 첫 번째 쿼리를 하위 쿼리 대신 상수로 사용하십시오.
Erwin Brandstetter

글쎄, 나는 새로운 테스트 테이블에서 하위 쿼리를 시도했다 CREATE TABLE test (mtid bigint NOT NULL, last_modifier bigint, CONSTRAINT test_property_pkey PRIMARY KEY (mtid)); CREATE INDEX test_last_modifier_btree ON test USING btree (last_modifier); INSERT INTO test (mtid, last_modifier) SELECT mtid, last_modifier FROM publication;. 그리고 동일한 쿼리에 대한 영향은 여전히 ​​존재했습니다 test. 하위 쿼리는 seq 스캔을 초래했습니다 ... 9.1과 9.4를 모두 시도했습니다. 효과는 같습니다.
P.Peter

1
@ 피터 : 테스트를 다시 실행하고 OR조건 없이 테스트했다는 것을 깨달았습니다 . 트릭 LIMIT은 더 간단한 경우에만 작동합니다.
Erwin Brandstetter

6

동료가 쿼리를 변경하여 간단한 다시 쓰기가 필요하고 수행해야 할 작업, 즉 한 단계에서 하위 선택을 수행 한 다음 결과에 대한 추가 작업을 수행 할 수있는 방법을 찾았습니다.

SELECT mtid FROM publication 
WHERE 
  mtid = ANY( (SELECT ARRAY(SELECT 9762715))::bigint[] )
  OR last_modifier=21321
LIMIT 5000;

Explain 분석은 다음과 같습니다.

 Limit  (cost=92.58..9442.38 rows=2478 width=8) (actual time=0.071..0.074 rows=1 loops=1)
   InitPlan 2 (returns $1)
     ->  Result  (cost=0.01..0.02 rows=1 width=0) (actual time=0.010..0.011 rows=1 loops=1)
           InitPlan 1 (returns $0)
             ->  Result  (cost=0.00..0.01 rows=1 width=0) (actual time=0.001..0.002 rows=1 loops=1)
   ->  Bitmap Heap Scan on publication  (cost=92.56..9442.36 rows=2478 width=8) (actual time=0.069..0.070 rows=1 loops=1)
         Recheck Cond: ((mtid = ANY (($1)::bigint[])) OR (last_modifier = 21321))
         Heap Blocks: exact=1
         ->  BitmapOr  (cost=92.56..92.56 rows=2478 width=0) (actual time=0.060..0.060 rows=0 loops=1)
               ->  Bitmap Index Scan on publication_pkey  (cost=0.00..44.38 rows=10 width=0) (actual time=0.046..0.046 rows=1 loops=1)
                     Index Cond: (mtid = ANY (($1)::bigint[]))
               ->  Bitmap Index Scan on publication_last_modifier_btree  (cost=0.00..46.94 rows=2468 width=0) (actual time=0.011..0.011 rows=0 loops=1)
                     Index Cond: (last_modifier = 21321)
 Planning time: 0.704 ms
 Execution time: 0.153 ms

이 방법으로 모든 하위 선택을 찾아 다시 쓰는 간단한 파서를 만들 수 있으며 기본 쿼리를 조작하기 위해 최대 절전 모드 후크에 추가 할 수 있습니다.


재미 있겠다. SELECT질문의 첫 번째 쿼리에서와 같이 모든을 제거하는 것이 쉽지 않습니까?
dezso

물론, 나는 두 단계 접근을 할 수 있습니다 : SELECT개별적으로 수행 한 다음 외부 선택을 정적 목록으로 수행하십시오 IN. 그러나 여분의 네트워크 왕복이 있고 postgres 형식으로 많은 결과를 얻은 다음 Java에서 결과를 구문 분석 한 다음 (결과를 수행하는 경우 하위 쿼리에 몇 가지 이상의 결과가있는 경우 5-10 배) 상당히 느립니다. 다시 거꾸로 동일). 위의 솔루션은 프로세스를 postgres 내부에 남겨두고 의미 적으로 동일하게 수행합니다. 대체로 이것은 현재 우리의 경우 가장 작은 수정으로 가장 빠른 방법 인 것 같습니다.
P.Peter

아, 알겠습니다 내가 모르는 것은 한 번에 많은 ID를 얻을 수 있다는 것입니다.
dezso

1

두 번째 질문에 대한 답변 : 예. 하위 쿼리에 ORDER BY를 추가하면 긍정적 인 영향을 미칩니다. 그러나 성능면에서 "EXISTS (하위 쿼리)"솔루션과 유사합니다. 하위 쿼리의 경우에도 두 행이 만들어 지므로 큰 차이가 있습니다.

SELECT mtid FROM publication
WHERE mtid IN (SELECT #column# ORDER BY #column#) OR last_modifier=21321
LIMIT 5000;
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.