PostgreSQL의 뷰가 성능에 유해합니까?


44

다음은 db 디자인 (Beginning Database Design ISBN : 0-7645-7490-6)에 관한 책에서 발췌 한 내용입니다.

뷰를 사용할 때의 위험은 뷰에 대해 쿼리를 필터링하여 매우 큰 테이블의 아주 작은 부분을 읽을 것으로 예상하는 것입니다. 뷰 자체에 대한 필터링은 뷰의 쿼리가 실행을 완료 한 후에 적용되므로 뷰 내에서 필터링을 수행해야합니다. 뷰는 일반적으로 개발 프로세스 속도를 높이는 데 유용하지만 장기적으로 데이터베이스 성능을 완전히 저하시킬 수 있습니다.

다음은 PostgreSQL 9.5 설명서에서 발췌 한 것입니다.

뷰를 자유롭게 사용하는 것은 좋은 SQL 데이터베이스 디자인의 핵심 요소입니다. 뷰를 사용하면 일관된 인터페이스 뒤에서 응용 프로그램이 발전함에 따라 변경 될 수있는 테이블 구조의 세부 정보를 캡슐화 할 수 있습니다.

두 소스는 서로 모순되는 것처럼 보입니다 ( "보기로 디자인하지 않음"과 "보기로 디자인하기").

그러나 PG에서보기는 규칙 시스템을 사용하여 구현됩니다. 따라서 뷰에 대한 필터링은 뷰 내에서 필터로 다시 작성되어 기본 테이블에 대해 단일 쿼리 실행이 발생할 수 있습니다.

내 해석이 정확하고 PG는 WHERE 절을 뷰 안팎으로 결합합니까? 아니면 하나씩 따로 실행합니까? 짧고 독립적이며 올바른 (컴파일 가능한) 예가 있습니까?


두 출처가 똑같은 것에 대해 이야기하지 않기 때문에 질문이 옳지 않다고 생각합니다. 첫 번째는 뷰의 쿼리와 관련이 있으며 AFTER는 필터를 적용합니다 SELECT * FROM my_view WHERE my_column = 'blablabla';. 두 번째는 뷰를 사용하여 데이터 모델을 사용하는 응용 프로그램에 데이터 모델을 투명하게 만드는 것입니다. 첫 번째 소스 WHERE my_column = 'blablabla'는 뷰 정의 내에 필터를 포함하도록 지시 하므로 실행 계획이 향상됩니다.
EAmez

답변:


48

책이 잘못되었습니다.

뷰에서 선택하는 것입니다 정확히 같은 빠르게 또는 느리게 기본 SQL 문을 실행으로 - 쉽게 사용하는 것을 확인할 수 있습니다 explain analyze.

Postgres 옵티 마이저 (및 다른 많은 최신 DBMS의 옵티 마이저)는 뷰의 술어를 실제 뷰 명령문으로 푸시 할 수 있습니다. 이는 단순한 명령문 인 경우 (다시 사용하여 확인할 수 있습니다 explain analyze).

성능에 관한 "나쁜 평판"은 뷰를 과도하게 사용하고 뷰를 사용하는 뷰를 사용하는 뷰를 구축 할 때부터 시작됩니다. 종종 일부 중간 테이블이 필요하지 않기 때문에 뷰없이 수동으로 작성된 명령문과 비교할 때 너무 많은 명령문이 발생합니다. 거의 모든 경우에 옵티마이 저는 불필요한 테이블 / 조인을 제거하거나 여러 레벨의 뷰에서 술어를 푸시 다운하기에 충분하지 않습니다 (이는 다른 DBMS에서도 마찬가지입니다).


3
제안 된 반대 답변 중 일부를 고려할 때 간단한 설명 이 무엇인지 설명 할 수 있습니다 .
RDFozz

explain analyze명세서 사용 방법을 설명해 주 시겠습니까?
Dustin Michels

@DustinMichels : 매뉴얼을 살펴보십시오 : postgresql.org/docs/current/using-explain.html
a_horse_with_no_name

19

당신에게 부여하려면 예를 들어 어떤 @a_horse 설명을 :

Postgres 는 DB 객체에 대한 정보를 표준화 된 형태로 제공하는 (때로는 복잡한) 보기 로 구성된 정보 스키마를 구현합니다 . 이것은 편리하고 신뢰할 수 있으며 Postgres 카탈로그 테이블에 직접 액세스하는 것보다 훨씬 비쌉니다.


정보 스키마에서 테이블의 모든 보이는 열을 가져 오는 매우 간단한 예 :

SELECT column_name
FROM   information_schema.columns
WHERE  table_name = 'big'
AND    table_schema = 'public';

... 시스템 카탈로그에서 :

SELECT attname
FROM   pg_catalog.pg_attribute
WHERE  attrelid = 'public.big'::regclass
AND    attnum > 0
AND    NOT attisdropped;

와 함께 쿼리 계획과 실행 시간을 비교하십시오 EXPLAIN ANALYZE.

  • 첫 번째 쿼리는 view를 기반으로하며 information_schema.columns,이 테이블은 전혀 필요하지 않은 여러 테이블에 조인됩니다.

  • 두 번째 쿼리는 하나의 테이블 만 스캔 pg_catalog.pg_attribute하므로 훨씬 빠릅니다. (그러나 첫 번째 쿼리는 여전히 공통 DB에서 몇 ms 만 필요합니다.)

세부:


7

편집하다:

사과를 통해 받아 들인 대답이 항상 정확하지는 않다는 주장을 철회해야합니다.보기는 항상 하위 쿼리로 작성된 것과 동일하다는 것을 나타냅니다. 나는 그것이 논쟁의 여지가 없다고 생각하며, 나는 지금 내 사건에서 무슨 일이 일어나고 있는지 알고 있다고 생각합니다.

나는 또한 원래 질문에 대한 더 나은 대답이 있다고 생각합니다.

원래 질문은 뷰를 사용하는 연습을 안내해야하는지에 관한 것입니다 (예를 들어, 두 번 이상 유지해야하는 루틴에서 SQL을 반복하는 것과는 반대).

내 쿼리는 "쿼리가 창 함수 또는 다른 기능을 사용하여 하위 쿼리가 될 때 옵티마이 저가 쿼리를 다르게 처리하게하는 경우가 아닙니다. 하위 쿼리를 작성하는 작업 (보기로 표시 되든 아니든)이 성능을 저하시킬 수 있기 때문입니다. 런타임에 매개 변수를 사용하여 필터링하는 경우

내 윈도우 기능의 복잡성은 불필요합니다. 이에 대한 설명 계획 :

SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            count(*) OVER 
              (PARTITION BY ts.train_service_key) AS train_records
FROM staging.train_service ts
   JOIN staging.portion_consist pc 
     USING (ds_code, train_service_key)
WHERE assembly_key = '185132';

이보다 훨씬 저렴합니다.

SELECT *
FROM (SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            count(*) OVER
              (PARTITION BY ts.train_service_key) AS train_records
FROM staging.train_service ts
   JOIN staging.portion_consist pc
     USING (ds_code, train_service_key)) AS query
WHERE assembly_key = '185132';

좀 더 구체적이고 도움이되기를 바랍니다.

최근의 경험에서 (이 질문을 찾게 되었기 때문에) 위의 대답은 모든 상황에서 정확하지 않습니다. 창 함수를 포함하는 비교적 간단한 쿼리가 있습니다.

SELECT DISTINCT ts.train_service_key,
                pc.assembly_key,
                dense_rank() OVER (PARTITION BY ts.train_service_key
                ORDER BY pc.through_idx DESC, pc.first_portion ASC,
               ((CASE WHEN (NOT ts.primary_direction)
                 THEN '-1' :: INTEGER
                 ELSE 1
                 END) * pc.first_seq)) AS coach_block_idx
FROM (staging.train_service ts
JOIN staging.portion_consist pc USING (ds_code, train_service_key))

이 필터를 추가하면 :

where assembly_key = '185132'

내가 얻는 설명 계획은 다음과 같습니다.

QUERY PLAN
Unique  (cost=11562.66..11568.77 rows=814 width=43)
  ->  Sort  (cost=11562.66..11564.70 rows=814 width=43)
    Sort Key: ts.train_service_key, (dense_rank() OVER (?))
    ->  WindowAgg  (cost=11500.92..11523.31 rows=814 width=43)
          ->  Sort  (cost=11500.92..11502.96 rows=814 width=35)
                Sort Key: ts.train_service_key, pc.through_idx DESC, pc.first_portion, ((CASE WHEN (NOT ts.primary_direction) THEN '-1'::integer ELSE 1 END * pc.first_seq))
                ->  Nested Loop  (cost=20.39..11461.57 rows=814 width=35)
                      ->  Bitmap Heap Scan on portion_consist pc  (cost=19.97..3370.39 rows=973 width=38)
                            Recheck Cond: (assembly_key = '185132'::text)
                            ->  Bitmap Index Scan on portion_consist_assembly_key_index  (cost=0.00..19.72 rows=973 width=0)
                                  Index Cond: (assembly_key = '185132'::text)
                      ->  Index Scan using train_service_pk on train_service ts  (cost=0.43..8.30 rows=1 width=21)
                            Index Cond: ((ds_code = pc.ds_code) AND (train_service_key = pc.train_service_key))

열차 서비스 테이블의 기본 키 인덱스와 part_consist 테이블의 고유하지 않은 인덱스를 사용하고 있습니다. 90ms에서 실행됩니다.

뷰를 만들었습니다 (절대적으로 명확하게 붙여 넣지 만 실제로는 뷰의 쿼리입니다).

CREATE OR REPLACE VIEW staging.v_unit_coach_block AS
SELECT DISTINCT ts.train_service_key,
            pc.assembly_key,
            dense_rank() OVER (PARTITION BY ts.train_service_key
              ORDER BY pc.through_idx DESC, pc.first_portion ASC, (
                (CASE
              WHEN (NOT ts.primary_direction)
                THEN '-1' :: INTEGER
              ELSE 1
              END) * pc.first_seq)) AS coach_block_idx
 FROM (staging.train_service ts
  JOIN staging.portion_consist pc USING (ds_code, train_service_key))

동일한 필터 로이보기를 쿼리하면 :

select * from staging.v_unit_coach_block
where assembly_key = '185132';

이것은 설명 계획입니다.

QUERY PLAN
Subquery Scan on v_unit_coach_block  (cost=494217.13..508955.10     rows=3275 width=31)
Filter: (v_unit_coach_block.assembly_key = '185132'::text)
 ->  Unique  (cost=494217.13..500767.34 rows=655021 width=43)
    ->  Sort  (cost=494217.13..495854.68 rows=655021 width=43)
          Sort Key: ts.train_service_key, pc.assembly_key, (dense_rank() OVER (?))
          ->  WindowAgg  (cost=392772.16..410785.23 rows=655021 width=43)
                ->  Sort  (cost=392772.16..394409.71 rows=655021 width=35)
                      Sort Key: ts.train_service_key, pc.through_idx DESC, pc.first_portion, ((CASE WHEN (NOT ts.primary_direction) THEN '-1'::integer ELSE 1 END * pc.first_seq))
                      ->  Hash Join  (cost=89947.40..311580.26 rows=655021 width=35)
                            Hash Cond: ((pc.ds_code = ts.ds_code) AND (pc.train_service_key = ts.train_service_key))
                            ->  Seq Scan on portion_consist pc  (cost=0.00..39867.86 rows=782786 width=38)
                            ->  Hash  (cost=65935.36..65935.36 rows=1151136 width=21)
                                  ->  Seq Scan on train_service ts  (cost=0.00..65935.36 rows=1151136 width=21)

이것은 두 테이블에서 전체 스캔을 수행하며 17 초가 걸립니다.

내가 이것을 만날 때까지 나는 PostgreSQL과 함께 뷰를 자유롭게 사용했습니다 (허용 된 답변으로 표현 된 광범위한 견해를 이해하게 함). 사전 집계 필터링이 필요한 경우 뷰 반환 기능을 사용하는 경우보기를 사용하지 않는 것이 좋습니다.

또한 PostgreSQL의 CTE는 설계 상 엄격하게 별도로 평가되므로 하위 쿼리로 최적화 된 것처럼 SQL Server와 같은 방식으로 사용하지 않습니다.

따라서 내 대답은 뷰가 기반으로하는 쿼리와 정확히 일치하지 않는 경우가 있으므로주의를 기울이는 것이 좋습니다. PostgreSQL 9.6.6 기반 Amazon Aurora를 사용하고 있습니다.


2
" 이것이 간단한 진술이라면 " - 다른 대답 의 경고에 주목하십시오 .
RDFozz

참고로, CASE WHEN (NOT ts.primary_direction) THEN '-1' :: INTEGER ELSE 1 END순서대로 두 개의 조건을 더 작성하는 것이 더 나을 때보 다 쿼리 속도가 느려질 수 있습니다.
Evan Carroll

@EvanCarroll 나는 이것을 잠시 동안 고투했다. CASE를 한 수준으로 끌어내는 것이 조금 더 빠름을 발견했습니다.CASE WHEN (NOT ts.primary_direction) THEN dense_rank() OVER (PARTITION BY ts.train_service_key ORDER BY pc.through_idx DESC, pc.first_portion ASC, pc.first_seq DESC) ELSE dense_rank() OVER (PARTITION BY ts.train_service_key ORDER BY pc.through_idx DESC, pc.first_portion ASC, pc.first_seq ASC) END AS coach_block_idx
enjayaitch

좋은 생각도 아닙니다. 여기 몇 가지 문제가 있습니다. 큰 관점은 뷰가 실제로 의미가 없으며 사용으로 인해 다른 일을 수행하므로 dense_rank()실제로 성능 문제가 아니라는 것입니다.
Evan Carroll

1
@EvanCarroll 귀하의 의견은 저에게 직접 도착하라는 메시지를 표시했습니다 (따라서 편집 된 답변). 감사합니다.
enjayaitch

-1

(나는 열망하는 팬이지만 여기 PG에 매우주의를 기울여야하며 쿼리 / 코드의 이해 및 유지 보수성을 높이기 위해 모든 사람들이 PG에서도 일반적으로보기를 사용하도록 권장하고 싶습니다 )

Postgres에서 뷰를 사용하여 실제로 슬프게도 (경고 :) 실제 문제를 일으켰으며 내부에서 사용하는 기능에 따라 성능이 크게 저하되었습니다 .- ((적어도 v10.1의 경우). Oracle과 같은 최신 DB 시스템)

따라서 뷰에 대해 필터링하면 기본 테이블에 대해 단일 쿼리 실행이 발생할 수 있습니다.

(정확히 의미하는 바에 따라-중간 임시 테이블이 원하지 않을 수도 있고 술어가 푸시되지 않는 곳에서 구체화 될 수 있습니다 ...)

나는 적어도 두 가지 주요 "기능을"알고 우리를 실망 중간에 포스트 그레스에 오라클 마이그레이션 우리는 프로젝트에 PG를 포기했다, 그래서 :

  • CTE ( with-clause 서브 쿼리 / 공통 테이블 표현식 )는 (보다 작은 애플리케이션에서도) 더 복잡한 쿼리를 구성하는 데 유용하지만 PG에서는 "숨겨진"옵티 마이저 힌트 (예 : 색인화되지 않은 임시 테이블 생성) 로 구현됩니다. 따라서 (나와 다른 많은 사람들에게 중요한) 선언적 SQL 개념 ( Oracle docu ) 을 위반 합니다 .

    • 간단한 쿼리 :

      explain
      
        select * from pg_indexes where indexname='pg_am_name_index'
      
      /* result: 
      
      Nested Loop Left Join  (cost=12.38..26.67 rows=1 width=260)
        ...
        ->  Bitmap Index Scan on pg_class_relname_nsp_index  (cost=0.00..4.29 rows=2 width=0)
                                               Index Cond: (relname = 'pg_am_name_index'::name)
        ...
      */
    • 일부 CTE를 사용하여 다시 작성 :

      explain
      
        with 
      
        unfiltered as (
          select * from pg_indexes
        ) 
      
        select * from unfiltered where indexname='pg_am_name_index'
      
      /* result:
      
      CTE Scan on unfiltered  (cost=584.45..587.60 rows=1 width=288)
         Filter: (indexname = 'pg_am_name_index'::name)
         CTE unfiltered
           ->  Hash Left Join  (cost=230.08..584.45 rows=140 width=260)  
      ...
      */
    • 토론 등의 추가 소스 : https://blog.2ndquadrant.com/postgresql-ctes-are-optimization-fences/

  • -over 문이 있는 창 함수는 사용 불가능할 수 있습니다 (일반적으로보기에서 더 복잡한 쿼리를 기반으로하는 보고서의 소스로 사용됨)


with-clauses에 대한 우리의 해결 방법

모든 "인라인 뷰"를 특수한 접두사가있는 실제 뷰로 변환하여 뷰의 목록 / 네임 스페이스를 망칠 수 없으며 원래 "외부 뷰"와 쉽게 관련 될 수 있습니다.


윈도우 기능을위한 솔루션

Oracle 데이터베이스를 사용하여 성공적으로 구현했습니다.


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