나는 많은 실험을 해왔으며 여기에 나의 발견이 있습니다.
진과 분류
현재 GIN 지수 (버전 9.4 기준) 는 주문을 지원할 수 없습니다 .
현재 PostgreSQL에서 지원하는 인덱스 유형 중에서 B- 트리 만 정렬 된 출력을 생성 할 수 있습니다. 다른 인덱스 유형은 지정되지 않은 구현 종속 순서로 일치하는 행을 반환합니다.
work_mem
이 구성 매개 변수 를 지적 해 주신 Chris에게 감사 합니다 . 기본값은 4MB이며 레코드 세트가 더 큰 경우 work_mem
적절한 값 (에서 찾을 수 있음)으로 증가 하면 EXPLAIN ANALYSE
정렬 작업 속도가 크게 향상 될 수 있습니다 .
ALTER SYSTEM SET work_mem TO '32MB';
변경 사항을 적용하려면 서버를 다시 시작한 다음 다시 확인하십시오.
SHOW work_mem;
원래 검색어
데이터베이스를 650k 제품으로 채우고 일부 카테고리는 최대 40k 제품을 보유했습니다. published
절 을 제거하여 쿼리를 약간 단순화했습니다 .
SELECT * FROM products WHERE category_ids @> ARRAY [248688]
ORDER BY score DESC, title LIMIT 10 OFFSET 30000;
Limit (cost=2435.62..2435.62 rows=1 width=1390) (actual time=1141.254..1141.256 rows=10 loops=1)
-> Sort (cost=2434.00..2435.62 rows=646 width=1390) (actual time=1115.706..1140.513 rows=30010 loops=1)
Sort Key: score, title
Sort Method: external merge Disk: 29656kB
-> Bitmap Heap Scan on products (cost=17.01..2403.85 rows=646 width=1390) (actual time=11.831..25.646 rows=41666 loops=1)
Recheck Cond: (category_ids @> '{248688}'::integer[])
Heap Blocks: exact=6471
-> Bitmap Index Scan on idx_products_category_ids_gin (cost=0.00..16.85 rows=646 width=0) (actual time=10.140..10.140 rows=41666 loops=1)
Index Cond: (category_ids @> '{248688}'::integer[])
Planning time: 0.288 ms
Execution time: 1146.322 ms
우리가 볼 수있는 work_mem
만큼 충분하지 않았기 때문에 Sort Method: external merge Disk: 29656kB
(여기의 숫자는 대략적인 것으로, 인 메모리 퀵 정렬을 위해서는 32MB 이상이 필요합니다)
메모리 풋 프린트 감소
정렬을 위해 전체 레코드를 선택하지 않고 ID를 사용하고 정렬, 오프셋 및 제한을 적용한 다음 필요한 레코드를 10 개만로드하십시오.
SELECT * FROM products WHERE id in (
SELECT id FROM products WHERE category_ids @> ARRAY[248688]
ORDER BY score DESC, title LIMIT 10 OFFSET 30000
) ORDER BY score DESC, title;
Sort (cost=2444.10..2444.11 rows=1 width=1390) (actual time=707.861..707.862 rows=10 loops=1)
Sort Key: products.score, products.title
Sort Method: quicksort Memory: 35kB
-> Nested Loop (cost=2436.05..2444.09 rows=1 width=1390) (actual time=707.764..707.803 rows=10 loops=1)
-> HashAggregate (cost=2435.63..2435.64 rows=1 width=4) (actual time=707.744..707.746 rows=10 loops=1)
Group Key: products_1.id
-> Limit (cost=2435.62..2435.62 rows=1 width=72) (actual time=707.732..707.734 rows=10 loops=1)
-> Sort (cost=2434.00..2435.62 rows=646 width=72) (actual time=704.163..706.955 rows=30010 loops=1)
Sort Key: products_1.score, products_1.title
Sort Method: quicksort Memory: 7396kB
-> Bitmap Heap Scan on products products_1 (cost=17.01..2403.85 rows=646 width=72) (actual time=11.587..35.076 rows=41666 loops=1)
Recheck Cond: (category_ids @> '{248688}'::integer[])
Heap Blocks: exact=6471
-> Bitmap Index Scan on idx_products_category_ids_gin (cost=0.00..16.85 rows=646 width=0) (actual time=9.883..9.883 rows=41666 loops=1)
Index Cond: (category_ids @> '{248688}'::integer[])
-> Index Scan using products_pkey on products (cost=0.42..8.45 rows=1 width=1390) (actual time=0.004..0.004 rows=1 loops=10)
Index Cond: (id = products_1.id)
Planning time: 0.682 ms
Execution time: 707.973 ms
참고 Sort Method: quicksort Memory: 7396kB
. 결과가 훨씬 좋습니다.
가입 및 추가 B- 트리 색인
Chris가 조언했듯이 추가 색인을 만들었습니다.
CREATE INDEX idx_test7 ON products (score DESC, title);
먼저 다음과 같이 합류했습니다.
SELECT * FROM products NATURAL JOIN
(SELECT id FROM products WHERE category_ids @> ARRAY[248688]
ORDER BY score DESC, title LIMIT 10 OFFSET 30000) c
ORDER BY score DESC, title;
쿼리 계획이 약간 다르지만 결과는 같습니다.
Sort (cost=2444.10..2444.11 rows=1 width=1390) (actual time=700.747..700.747 rows=10 loops=1)
Sort Key: products.score, products.title
Sort Method: quicksort Memory: 35kB
-> Nested Loop (cost=2436.05..2444.09 rows=1 width=1390) (actual time=700.651..700.690 rows=10 loops=1)
-> HashAggregate (cost=2435.63..2435.64 rows=1 width=4) (actual time=700.630..700.630 rows=10 loops=1)
Group Key: products_1.id
-> Limit (cost=2435.62..2435.62 rows=1 width=72) (actual time=700.619..700.619 rows=10 loops=1)
-> Sort (cost=2434.00..2435.62 rows=646 width=72) (actual time=697.304..699.868 rows=30010 loops=1)
Sort Key: products_1.score, products_1.title
Sort Method: quicksort Memory: 7396kB
-> Bitmap Heap Scan on products products_1 (cost=17.01..2403.85 rows=646 width=72) (actual time=10.796..32.258 rows=41666 loops=1)
Recheck Cond: (category_ids @> '{248688}'::integer[])
Heap Blocks: exact=6471
-> Bitmap Index Scan on idx_products_category_ids_gin (cost=0.00..16.85 rows=646 width=0) (actual time=9.234..9.234 rows=41666 loops=1)
Index Cond: (category_ids @> '{248688}'::integer[])
-> Index Scan using products_pkey on products (cost=0.42..8.45 rows=1 width=1390) (actual time=0.004..0.004 rows=1 loops=10)
Index Cond: (id = products_1.id)
Planning time: 1.015 ms
Execution time: 700.918 ms
다양한 오프셋과 제품 수로 재생하면 PostgreSQL에서 추가 B-tree 인덱스를 사용할 수 없습니다.
그래서 고전적인 길을 가고 접합 테이블을 만들었습니다 .
CREATE TABLE prodcats AS SELECT id AS product_id, unnest(category_ids) AS category_id FROM products;
CREATE INDEX idx_prodcats_cat_prod_id ON prodcats (category_id, product_id);
SELECT p.* FROM products p JOIN prodcats c ON (p.id=c.product_id)
WHERE c.category_id=248688
ORDER BY p.score DESC, p.title LIMIT 10 OFFSET 30000;
Limit (cost=122480.06..122480.09 rows=10 width=1390) (actual time=1290.360..1290.362 rows=10 loops=1)
-> Sort (cost=122405.06..122509.00 rows=41574 width=1390) (actual time=1264.250..1289.575 rows=30010 loops=1)
Sort Key: p.score, p.title
Sort Method: external merge Disk: 29656kB
-> Merge Join (cost=50.46..94061.13 rows=41574 width=1390) (actual time=117.746..182.048 rows=41666 loops=1)
Merge Cond: (p.id = c.product_id)
-> Index Scan using products_pkey on products p (cost=0.42..90738.43 rows=646067 width=1390) (actual time=0.034..116.313 rows=210283 loops=1)
-> Index Only Scan using idx_prodcats_cat_prod_id on prodcats c (cost=0.43..1187.98 rows=41574 width=4) (actual time=0.022..7.137 rows=41666 loops=1)
Index Cond: (category_id = 248688)
Heap Fetches: 0
Planning time: 0.873 ms
Execution time: 1294.826 ms
여전히 B- 트리 인덱스를 사용하지 않아 결과 집합이 맞지 work_mem
않아 결과가 좋지 않습니다.
그러나 상황에 따라 많은 제품 과 작은 오프셋을 가진 PostgreSQL은 이제 B- 트리 인덱스를 사용하기로 결정합니다.
SELECT p.* FROM products p JOIN prodcats c ON (p.id=c.product_id)
WHERE c.category_id=248688
ORDER BY p.score DESC, p.title LIMIT 10 OFFSET 300;
Limit (cost=3986.65..4119.51 rows=10 width=1390) (actual time=264.176..264.574 rows=10 loops=1)
-> Nested Loop (cost=0.98..552334.77 rows=41574 width=1390) (actual time=250.378..264.558 rows=310 loops=1)
-> Index Scan using idx_test7 on products p (cost=0.55..194665.62 rows=646067 width=1390) (actual time=0.030..83.026 rows=108037 loops=1)
-> Index Only Scan using idx_prodcats_cat_prod_id on prodcats c (cost=0.43..0.54 rows=1 width=4) (actual time=0.001..0.001 rows=0 loops=108037)
Index Cond: ((category_id = 248688) AND (product_id = p.id))
Heap Fetches: 0
Planning time: 0.585 ms
Execution time: 264.664 ms
여기서 B- 트리 인덱스는 직접적인 결과를 생성하지 않으므로 순차 검색을위한 가이드로만 사용되므로 실제로 매우 논리적입니다.
GIN 쿼리와 비교해 봅시다 :
SELECT * FROM products WHERE id in (
SELECT id FROM products WHERE category_ids @> ARRAY[248688]
ORDER BY score DESC, title LIMIT 10 OFFSET 300
) ORDER BY score DESC, title;
Sort (cost=2519.53..2519.55 rows=10 width=1390) (actual time=143.809..143.809 rows=10 loops=1)
Sort Key: products.score, products.title
Sort Method: quicksort Memory: 35kB
-> Nested Loop (cost=2435.14..2519.36 rows=10 width=1390) (actual time=143.693..143.736 rows=10 loops=1)
-> HashAggregate (cost=2434.71..2434.81 rows=10 width=4) (actual time=143.678..143.680 rows=10 loops=1)
Group Key: products_1.id
-> Limit (cost=2434.56..2434.59 rows=10 width=72) (actual time=143.668..143.670 rows=10 loops=1)
-> Sort (cost=2433.81..2435.43 rows=646 width=72) (actual time=143.642..143.653 rows=310 loops=1)
Sort Key: products_1.score, products_1.title
Sort Method: top-N heapsort Memory: 68kB
-> Bitmap Heap Scan on products products_1 (cost=17.01..2403.85 rows=646 width=72) (actual time=11.625..31.868 rows=41666 loops=1)
Recheck Cond: (category_ids @> '{248688}'::integer[])
Heap Blocks: exact=6471
-> Bitmap Index Scan on idx_products_category_ids_gin (cost=0.00..16.85 rows=646 width=0) (actual time=9.916..9.916 rows=41666 loops=1)
Index Cond: (category_ids @> '{248688}'::integer[])
-> Index Scan using products_pkey on products (cost=0.42..8.45 rows=1 width=1390) (actual time=0.004..0.004 rows=1 loops=10)
Index Cond: (id = products_1.id)
Planning time: 0.630 ms
Execution time: 143.921 ms
GIN의 결과가 훨씬 좋습니다. 상황에 따라 정션 테이블 접근 방식이 더 나아진 것은 아닙니다. 다양한 제품 조합과 오프셋을 확인했습니다 .
실 지수의 힘
PostgreSQL이 정렬을 위해 인덱스를 완전히 활용 하려면 매개 변수 WHERE
뿐만 아니라 모든 쿼리 매개 변수 ORDER BY
가 단일 B- 트리 인덱스에 있어야합니다. 이를 위해 정렬 필드를 제품에서 정션 테이블로 복사했습니다.
CREATE TABLE prodcats AS SELECT id AS product_id, unnest(category_ids) AS category_id, score, title FROM products;
CREATE INDEX idx_prodcats_1 ON prodcats (category_id, score DESC, title, product_id);
SELECT * FROM products WHERE id in (SELECT product_id FROM prodcats WHERE category_id=248688 ORDER BY score DESC, title LIMIT 10 OFFSET 30000) ORDER BY score DESC, title;
Sort (cost=2149.65..2149.67 rows=10 width=1390) (actual time=7.011..7.011 rows=10 loops=1)
Sort Key: products.score, products.title
Sort Method: quicksort Memory: 35kB
-> Nested Loop (cost=2065.26..2149.48 rows=10 width=1390) (actual time=6.916..6.950 rows=10 loops=1)
-> HashAggregate (cost=2064.83..2064.93 rows=10 width=4) (actual time=6.902..6.904 rows=10 loops=1)
Group Key: prodcats.product_id
-> Limit (cost=2064.02..2064.71 rows=10 width=74) (actual time=6.893..6.895 rows=10 loops=1)
-> Index Only Scan using idx_prodcats_1 on prodcats (cost=0.56..2860.10 rows=41574 width=74) (actual time=0.010..6.173 rows=30010 loops=1)
Index Cond: (category_id = 248688)
Heap Fetches: 0
-> Index Scan using products_pkey on products (cost=0.42..8.45 rows=1 width=1390) (actual time=0.003..0.003 rows=1 loops=10)
Index Cond: (id = prodcats.product_id)
Planning time: 0.318 ms
Execution time: 7.066 ms
그리고 이것은 선택된 카테고리에 많은 수의 제품과 큰 오프셋이있는 최악의 시나리오입니다. 오프셋 = 300 일 때 실행 시간은 0.5ms에 불과합니다.
불행히도 이러한 정션 테이블을 유지 보수 하려면 추가 노력이 필요합니다. 인덱싱 된 구체화 된 뷰를 통해 수행 할 수 있지만, 데이터를 거의 업데이트하지 않는 경우에만 유용합니다. 구체화 된 뷰를 새로 고치는 것이 상당히 큰 작업입니다.
그래서 work_mem
메모리 풋 프린트 쿼리 가 증가 하고 감소 하면서 지금까지 GIN 인덱스를 유지하고 있습니다.