PostgreSQL Practice & Tips - 3 - 실행 계획

13633 단어
계획 을 집행 하 는 것 이 무엇 입 니까?
모든 조회 요청 에 대해 PostgreSQL 은 그 위 에 실행 계획 (조회 계획) 을 설정 하고 조회 문구 구조 와 데이터 구 조 를 통 해 조회 계획 을 정확하게 설정 할 수 있 는 설정 은 시스템 성능 을 현저히 향상 시 킬 수 있 습 니 다. 우리 에 게 는 조회 계획 을 통 해 느 린 조회 의 원인 을 찾 는 경우 가 많 습 니 다. 가장 중요 한 성능 도구 라 고 할 수 있 습 니 다.물론 계획 을 집행 하 는 분석 과 최적화 가 어렵 기 때문에 본 편 은 기본 적 인 점 을 덮어 쓰 려 고 한다.
일반적으로 사용 EXPLAIN 은 조회 계획 을 표시 할 수 있 습 니 다. 물론 일부 option 을 사용 하여 이 를 바 꿀 수 있 습 니 다. 예 를 들 어 흔히 볼 수 있 는 ANALYZE COSTS 등 입 니 다.가장 많이 사용 되 는 것 은 ANALYZE 일 수 있 습 니 다. 실제 실 행 된 SQL 을 통 해 실제 실행 계획 을 얻 기 때문에 구체 적 인 시간 소비 와 줄 수 를 볼 수 있 습 니 다. 만약 에 INSERT 나 DELETE 가 데 이 터 를 얻 으 면 데이터 베 이 스 를 수정 할 것 입 니 다.따라서 BEGIN 를 사용 하여 EXPLAIN ANALYZE 을 하나의 업무 에 포함 시 키 고 실행 이 끝 난 후에 스크롤 백 할 수 있 습 니 다.만약 당신 이 사용 하지 않 는 다 면 ANALYZE PostgreSQL 은 무 작위 샘플, 기 존의 성능 데이터, 또는 추정 등 수단 으로 cost 를 추측 하기 때문에 정확 하지 않 지만 성능 을 충분히 분석 할 수 있 습 니 다. 특히 느 린 조 회 는 오래 기다 릴 수 있 고 사용 ANALYZE 은 매우 비효 율 적 입 니 다.
사실 공식 문 서 는 가장 좋 은 해석 입 니 다. 다른 options 나 깊이 공부 하고 싶 은 것 은 참고 하 시기 바 랍 니 다.
출력 결과 설명
우리 먼저 실제 해 보 자 EXPLAIN.
CREATE SEQUENCE user_id_seq;

CREATE TABLE users (
    id BIGINT NOT NULL DEFAULT nextval('user_id_seq'),
    type VARCHAR(10) NOT NULL,
    name VARCHAR(128) NOT NULL,
    address TEXT,
    married BOOLEAN DEFAULT false,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT now(),
    PRIMARY KEY (id)
);

#          

INSERT INTO users (type, name, address)
SELECT 'testing', left(md5(i::text), 10), left(md5(random()::text), 50)
FROM generate_series(1, 100000) s(i);

그래서 우리 가 진행 한 결 과 는 다음 과 같다.
EXPLAIN select * from users;
                          QUERY PLAN                          
--------------------------------------------------------------
 Seq Scan on users  (cost=0.00..1600.80 rows=26680 width=369)
(1 row)
  • EXPLAIN 우리 가 흔히 말 하 는 전체 표 스 캔, 처음부터 끝까지 의 순서 스 캔 을 나타 낸다.
  • Seq Scan 는 비용 이 든다 고 밝 혔 고 cost=0.00..1600.80 는 첫 줄 을 얻 는 데 얼마의 비용 이 필요 하 다 고 밝 혔 으 며 0.00 는 모두 되 돌아 가 는 데 얼마의 비용 이 필요 하 다 고 밝 혔 다.
  • 1600.80 몇 줄 로 돌아 가 는 지 표시
  • rows 줄 당 평균 몇 바이트 (rows 와 width 로 데이터 크기 를 추산 할 수 있 음)
  • cost 를 말 하기 전에 우 리 는 rows 의 데이터 가 정확 하지 않다 는 것 을 알 게 되 었 습 니 다. 이 표 에는 100, 000 개의 데이터 가 있 는데 어떻게 계획 안에 26, 680 개 만 있 는 지 조회 합 니까?우리 가 이전에 언급 한 width 은 많은 성능 데이터 에 근거 하여 추측 할 것 이다. 왜냐하면 이 표 가 막 세 워 졌 기 때문에 데이터 가 충분 하지 않 아서 결과 의 편차 가 매우 크다.너 는 EXPLAIN 문 구 를 만 든 후에 방금 SELECT COUNT(*) 문 구 를 다시 시도 해 보면 결과 가 크게 달라 질 것 이다.EXPLAIN 우리 의 실제 실행 시간 과 연결 되 지 않 습 니 다. 다만 PostgreSQL 은 실행 원 가 를 설명 하 는 단 위 를 사용 합 니 다. 이 물건 은 그 어떠한 시간 단위 와 도 환산 할 수 없습니다. PSQL 은 이렇게 정의 합 니 다. 예 를 들 어 cost 의 대 가 는 1.0 입 니 다. 한 줄 의 데 이 터 를 처리 하 는 대 가 는 0.01 입 니 다. 물론 이런 상수 들 도 스스로 수정 할 수 있 습 니 다.다른 표현 은 공식 문 서 를 참고 할 수 있 습 니 다. 일반적인 상황 에서 cost 가 무엇 인지 특별히 알 필요 가 없습니다. 성능 분석 만 할 수 있 으 면 충분 합 니 다.
    내 려 와 서 seq_page_cost 사용 후의 효 과 를 살 펴 보 자.
    EXPLAIN ANALYZE select * from users;
                                                      QUERY PLAN
    -----------------------------------------------------------------
     Seq Scan on users  (cost=0.00..2334.00 rows=100000 width=77) (actual time=0.013..631.094 rows=100000
     loops=1)
     Planning time: 0.068 ms
     Execution time: 1249.210 ms
    (3 rows)
    

    결과 에 구체 적 인 조회 시간 과 데 이 터 를 되 돌려 주 는 총 시간 이 있 습 니 다. ANALYZE 를 사용 하여 캐 시 명중 상황 을 볼 수 있 습 니 다.
    EXPLAIN (ANALYZE true, BUFFERS true) select * from users;
                                                      QUERY PLAN                                         
    -----------------------------------------------------------------
     Seq Scan on users  (cost=0.00..2334.00 rows=100000 width=77) (actual time=0.020..634.054 rows=100000
     loops=1)
       Buffers: shared hit=1334
     Planning time: 0.223 ms
     Execution time: 1254.985 ms
    (4 rows)
    

    1334 개의 block 을 명중 시 키 는 것 을 볼 수 있 습 니 다. 특히 데이터 시트 의 prewarm 을 만 든 후 캐 시 명중 도 필요 합 니 다. 이 를 통 해 buffers 의 효 과 를 평가 할 수 있 습 니 다.
    스캐닝
    PostgreSQL 의 스 캔 은 세 가지 가 있 습 니 다. 순서 스 캔 pg_prewarm, 색인 스 캔 seq scan 과 비트 맵 스 캔 index scan 이 있 습 니 다. 순서 스 캔 은 전체 표 스 캔 이 라 고도 부 릅 니 다. 표 의 모든 데이터 블록 을 처음부터 끝까지 스 캔 한 다음 에 조건 에 맞 는 데 이 터 를 얻 는 것 입 니 다. 색인 스 캔 은 색인 에서 필요 한 데이터 의 위 치 를 찾 은 다음 에 데 이 터 를 추출 하 는 과정 입 니 다.다음은 두 가지 예 가 있다.
    EXPLAIN select * from users;
                              QUERY PLAN                          
    --------------------------------------------------------------
     Seq Scan on users  (cost=0.00..2334.00 rows=100000 width=77)
    (1 row)
    
    EXPLAIN select * from users where id = 9999;
                                   QUERY PLAN                                
    -------------------------------------------------------------------------
     Index Scan using users_pkey on users  (cost=0.29..8.31 rows=1 width=77)
       Index Cond: (id = 9999)
    (2 rows)
    

    첫 번 째 조 회 는 표 의 100, 000 개의 데 이 터 를 검색 할 것 입 니 다. where 자구 가 없 기 때문에 이 조 회 는 seq scan 만 있 고 두 번 째 조 회 는 색인 에서 직접 검색 할 것 입 니 다 (두 번 째 편 에 있 는 B - Tree Index 를 기억 하 십 니까? 이 를 검색 하 는 과정 은 Index Scan 입 니 다).
    한편, 비트 맵 스 캔 bitmap scan 도 색인 스 캔 방식 이다. 원 리 는 색인 스 캔 을 하고 조건 을 만족 시 키 는 줄 의 지침 bitmap scan 을 즉시 꺼 내 메모리 에 저 장 된 비트 맵 에 저장 하 는 것 이다. 스 캔 이 끝 난 후에 비트 맵 의 tuple-pointer 을 실제 데이터 블록 에 있 는 데 이 터 를 읽 는 것 이다.이러한 조회 방식 은 비 등가 조회 의 경우 에 자주 사용 되 며, 두 개의 색인 을 걸 으 면 두 개의 색인 비트 맵 tuple-pointerand 의 계산 을 통 해 새로운 비트 맵 으로 합 쳐 그 위치 에 따라 실제 데 이 터 를 추출 할 수 있 으 며, 이 과정 에서 각 데이터 블록 은 스 캔 에서 한 번 만 읽 혔 다.예 를 들 어 우 리 는 특정한 색인 에 대해 조건 조 회 를 한다.
    #         scan,     bitmap scan
    SET enable_indexscan = off;
    SET enable_seqscan = off;
    
    #     
    
    CREATE INDEX users_name ON users (name);
    
    #     
    EXPLAIN SELECT * FROM users WHERE name IN ('a', 'b', 'c');
    
                                    QUERY PLAN                                
    --------------------------------------------------------------------------
     Bitmap Heap Scan on users  (cost=13.28..24.89 rows=3 width=77)
       Recheck Cond: ((name)::text = ANY ('{a,b,c}'::text[]))
       ->  Bitmap Index Scan on users_name  (cost=0.00..13.28 rows=3 width=0)
             Index Cond: ((name)::text = ANY ('{a,b,c}'::text[]))
    (4 rows)
    

    Recheck 의 이 유 는 MVCC 때문에 데 이 터 를 읽 은 후에 조건 을 다시 확인 해 야 하기 때 문 입 니 다.
    EXPLAIN SELECT * FROM users WHERE name IN ('a', 'b', 'c') OR name IN ('p', 'b', 'k');
                                    
                                    QUERY PLAN          
    --------------------------------------------------------------------------
     Bitmap Heap Scan on users  (cost=26.56..49.45 rows=6 width=77)
       Recheck Cond: (((name)::text = ANY ('{a,b,c}'::text[])) OR ((name)::text = ANY ('{p,b,k}'::text[])
    ))
       ->  BitmapOr  (cost=26.56..26.56 rows=6 width=0)
             ->  Bitmap Index Scan on users_name  (cost=0.00..13.28 rows=3 width=0)
                   Index Cond: ((name)::text = ANY ('{a,b,c}'::text[]))
             ->  Bitmap Index Scan on users_name  (cost=0.00..13.28 rows=3 width=0)
                   Index Cond: ((name)::text = ANY ('{p,b,k}'::text[]))
    (7 rows)
    

    이것 은 bitmap scanOR 의 예 입 니 다. 저 희 는 OR 를 사용 하여 두 개의 bitmap 를 합 친 다음 에 마지막 으로 데 이 터 를 읽 습 니 다.
    조건, 정렬 등
    조건, 정렬, limit 등 조작 에 대해 서도 해당 하 는 조회 계획 이 있 습 니 다. scan 보다 쉽게 이해 할 수 있 습 니 다. 예 를 들 어 다음 조회 계획:
    EXPLAIN SELECT * FROM users WHERE id > 100 ORDER BY created_at DESC LIMIT 10;
    
                                   QUERY PLAN                                
    -------------------------------------------------------------------------
     Limit  (cost=4742.89..4742.91 rows=10 width=77)
       ->  Sort  (cost=4742.89..4992.65 rows=99904 width=77)
             Sort Key: created_at
             ->  Seq Scan on users  (cost=0.00..2584.00 rows=99904 width=77)
                   Filter: (id > 100)
    (5 rows)
    
    or limit sort 는 모두 계획 에서 정렬 이 매우 비 싼 조작 (cost = 4742.8.9. 4992.65) 임 을 알 수 있 고 합 리 적 인 사용 순 서 는 성능 을 현저히 향상 시 킬 수 있다.
    Join
    PostgreSQL 은 3 가지 JOIN 방식 filter nestloop join hash join 이 있 으 며, PostgreSQL 은 데이터 양의 크기 와 성능 의 통계 데이터 등에 따라 적합 한 방식 을 선택한다.merge join 내장 순환 이 라 고도 하 는데 말 그대로 내장 순환 방식 을 사용 하여 먼저 외모 에서 데 이 터 를 얻 은 다음 에 내 표를 찾 아 일치 하 는 것 이다.그래서 이런 방식 은 데이터 양 이 비교적 많은 상황, 예 를 들 어 줄 수가 수만 에 달 하 는 상황 에 적합 하지 않다.분명 한 복잡 도 는 O (M * N) 이 고 M 과 N 은 join 이 필요 한 데이터 양 이지 만 nestloop join 이 효율 적 이지 않 아 보이 더 라 도 이런 방식 은 모든 다 중 표 연결 검사 상황 을 계산 할 수 있 는 가장 기본 적 인 기능 이 라 고 볼 수 있다.
    그러나 inner 표 에 join 에 사용 되 는 필드 에 색인 이 존재 한다 면 nestloop join 의 효율 이 약간 좋 을 것 입 니 다. 우 리 는 B - Tree 의 조회 복잡 도가 log (N) 라 는 것 을 알 기 때문에 index scan nestloop join 에서 O(M*log(N)) 보다 좋 습 니 다 nestloop join.
    다음은 nestloop JOIN 의 인 스 턴 스 이 며, Index Scan 이 있 습 니 다. O(M*N) 전체 표를 스 캔 한 후, 모든 데 이 터 를 JOIN 하여 users 를 조회 하 는 것 을 볼 수 있 습 니 다.2 표 에 조건 을 만족 시 키 는 데이터 가 있 는 지, 즉 사용 users.
    #     nestloop 
    SET enable_nestloop = ON;
    SET enable_hashjoin = OFF;
    SET enable_mergejoin = OFF;
    
    EXPLAIN SELECT * FROM users JOIN users_2 ON users.id = users_2.id;
    
                                        QUERY PLAN                                     
    -----------------------------------------------------------------------------------
     Nested Loop  (cost=0.42..79748.00 rows=100000 width=154)
       ->  Seq Scan on users  (cost=0.00..2334.00 rows=100000 width=77)
       ->  Index Scan using users_2_pkey on users_2  (cost=0.42..0.76 rows=1 width=77)
             Index Cond: (id = users.id)
    
    Index Condhash join 보다 약간 앞서 있다. 일반적으로 PostgreSQL 조회 최적화 기 는 두 개의 표 중 작은 표를 사용 하여 메모리 에 넣 고 하나의 산 목록 을 만 든 다음 에 큰 표를 스 캔 하고 메모리 의 산 목록 을 조회 한 다음 에 데 이 터 를 찾아낸다.이 경우 조인 할 때 작은 시 계 를 메모리 에 완전히 넣 는 것 이 좋 습 니 다. 그러나 작은 데이터 시트 가 메모리 에 모두 넣 지 못 하면 PostgreSQL 은 파 티 션 을 나 누고 메모리 에 넣 을 수 없 는 부분 을 임시 세그먼트 에 기록 합 니 다. 그러면 많은 I / O 비용 이 필요 합 니 다.
    #   hash join
    SET enable_nestloop = OFF;
    SET enable_hashjoin = ON;
    SET enable_mergejoin = OFF;
    
    EXPLAIN SELECT * FROM users JOIN users_2 ON users.id = users_2.id;
    
                                    QUERY PLAN                                
    --------------------------------------------------------------------------
     Hash Join  (cost=4854.00..36487.00 rows=100000 width=154)
       Hash Cond: (users_2.id = users.id)
       ->  Seq Scan on users_2  (cost=0.00..11667.00 rows=500000 width=77)
       ->  Hash  (cost=2334.00..2334.00 rows=100000 width=77)
             ->  Seq Scan on users  (cost=0.00..2334.00 rows=100000 width=77)
    

    이 조회 계획 은 매우 명확 합 니 다. 먼저 표 users 에 게 seq scan 을 한 다음 에 그 결 과 를 메모리 에 넣 은 hash 입 니 다. 표 users 는 100, 000 개의 데이터 만 있 고 작은 표 이 며 users2 표 에서 seq scan 을 한 다음 hash 에 hash join 을 들 어가 기 때문에 그 결과 의 총 cost 는 nestloop 보다 작 습 니 다. 즉, 36487.00 < 79748.00 이지 만 시작 시간 (첫 번 째 데이터 가 돌아 오 는 시간) 은 nestloop 즉 4854.00 > 0.42 보다 큽 니 다. hash table 을 만들어 야 하기 때 문 입 니 다.
    일반적으로 nestloop 의 효 과 는 hash join 보다 좋 지만, JOIN 의 필드 에 색인 이 있 거나 정렬 (예 를 들 어 JOIN 의 결과 표) 이 필요 하 다 면 오랫동안 hash 를 만 들 필요 가 없다. 이때 merge join 의 성능 은 merge join 보다 좋 을 것 이다.그래서 위의 예 에서 모든 JOIN 옵션 을 열 면 PostgreSQL 은 기본적으로 더 효율 적 인 hash join 을 사용 합 니 다.
    #       JOIN   
    SET enable_nestloop = ON;
    SET enable_hashjoin = ON;
    SET enable_mergejoin = ON;
    EXPLAIN SELECT * FROM users JOIN users_2 ON users.id = users_2.id;
    
                                             QUERY PLAN                                         
    --------------------------------------------------------------------------------------------
     Merge Join  (cost=1.60..9351.82 rows=100000 width=154)
       Merge Cond: (users.id = users_2.id)
       ->  Index Scan using users_pkey on users  (cost=0.29..3941.29 rows=100000 width=77)
       ->  Index Scan using users_2_pkey on users_2  (cost=0.42..19666.42 rows=500000 width=77)
    

    색인 은 이미 정렬 되 어 있 기 때문에 두 개의 Index Scan 은 색인 을 직접 스 캔 하여 예상 되 는 cost 가 세 가지 상황 중에서 가장 좋 은 것 을 볼 수 있 습 니 다.
    성능 최적화
    이전 예 에서 PostgreSQL 이 서로 다른 조회 계획 을 강제 적 으로 실행 하도록 제어 하 는 유사 한 merge join 인 자 를 사 용 했 습 니 다. 그렇지 않 으 면 PostgreSQL 의 최적화 기 는 가장 좋 은 상황 을 선택 하여 실 행 했 기 때 문 입 니 다. 예 를 들 어 JOIN 의 예 에서 merge join 을 사용 하기 때 문 입 니 다.
    일반적인 상황 에서 PostgreSQL 은 조회 계획 을 잘못 실행 하지 않 습 니 다. 가끔 은 데이터베이스 서 비 스 를 시작 하거나 새 표를 사용 할 때 발생 합 니 다. 그 때 는 최적화 기 가 결정 하 는 데 도움 이 되 는 통계 정보 가 충분 하지 않 았 기 때 문 입 니 다.따라서 프로젝트 에서 특별한 상황 이 없 으 면 이러한 enable_mergejoin 인 자 를 수 동 으로 사용 하여 계획 을 강제 집행 할 필요 가 없다.
    또한 PostgreSQL 은 enable_mergejoin 명령 을 사용 하여 통계 표 의 정 보 를 수집 할 수 있 으 며, 이러한 통계 정 보 는 최적화 기 가 적절 한 실행 계획 을 선택 하 는 데 도움 이 될 것 이다.그러나 일반적으로 PostgreSQL 은 기본적으로 하나의 analyze 프로 세 스 를 실행 하여 vacuum 작업 을 하고 모든 표를 자동 으로 분석 합 니 다. 가끔 우 리 는 이 프로 세 스 를 닫 고 주기 적 이 고 수 동 으로 vacuum 과 analysis (예 를 들 어 한밤중) 를 진행 합 니 다.그러나 특별한 상황 이 없 으 면 수 동 분석 을 할 때 범 하기 쉬 운 인위적인 실 수 를 피 하 는 경향 이 있 습 니 다.더 공부 하고 싶 으 시 면 공식 문 서 를 참고 하 셔 도 됩 니 다. 다음 업데이트 에서 VACUUM 에 대해 구체 적 으로 말씀 드 리 겠 습 니 다.
    우리 가 느 린 조회 에 직면 한 첫 번 째 일 은 실행 계획 을 분석 하 는 것 이다. 예 를 들 어 자주 하 는 방법 은 Seq Scan 을 Index Scan 으로 바 꾸 거나 Nestloop 을 취소 하 는 것 이다.일반적인 상황 에서 검색 어 를 바 꿀 수 없다 면 실행 계획 을 통 해 구체 적 인 실행 경로 와 원 가 를 얻 은 다음 에 맞 춤 형 최적화 를 한다.

    좋은 웹페이지 즐겨찾기