ฉันทำการทดลองมากมายและนี่คือสิ่งที่ฉันค้นพบ
GIN และการเรียงลำดับ
ดัชนี GIN ในปัจจุบัน (ตั้งแต่เวอร์ชั่น 9.4) ไม่สามารถช่วยเหลือการสั่งซื้อได้
ในขณะนี้ประเภทดัชนีที่รองรับโดย PostgreSQL มีเพียง B-tree เท่านั้นที่สามารถสร้างเอาต์พุตที่เรียงลำดับได้ดัชนีประเภทอื่น ๆ จะส่งคืนแถวที่ตรงกันในลำดับที่ไม่ได้ระบุและขึ้นอยู่กับการใช้งาน
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 สำหรับหน่วยความจำด่วนในหน่วยความจำเล็กน้อย)
ลดขนาดหน่วยความจำ
อย่าเลือกระเบียนเต็มรูปแบบสำหรับการเรียงลำดับใช้รหัสใช้เรียงลำดับชดเชยและ จำกัด แล้วโหลดเพียง 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-tree เพิ่มเติม
ตามที่ 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
ผลลัพธ์ไม่พอดีดังนั้นผลลัพธ์ที่ไม่ดี
แต่ภายใต้สถานการณ์บางอย่างการมีผลิตภัณฑ์จำนวนมากและออฟเซ็ตออฟเซ็ตเล็ก ๆ ในตอนนี้ตัดสินใจใช้ดัชนีทรี 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-tree ที่นี่ไม่ได้ให้ผลลัพธ์โดยตรง แต่จะใช้เป็นแนวทางในการสแกนตามลำดับเท่านั้น
ลองเปรียบเทียบกับแบบสอบถาม 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-tree เดียว เมื่อต้องการทำสิ่งนี้ฉันได้คัดลอกเขตข้อมูลเรียงลำดับจากผลิตภัณฑ์ไปยังตารางแยก:
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
และนี่เป็นสถานการณ์ที่เลวร้ายที่สุดที่มีผลิตภัณฑ์จำนวนมากในหมวดหมู่ที่เลือกและออฟเซ็ตขนาดใหญ่ เมื่อ offset = 300 เวลาดำเนินการเพียง 0.5 ms
น่าเสียดายที่การบำรุงรักษาตารางแยกดังกล่าวต้องการความพยายามพิเศษ มันสามารถทำได้ผ่านมุมมอง materialized ดัชนี แต่ที่เป็นประโยชน์เฉพาะเมื่อข้อมูลของคุณไม่ค่อยปรับปรุงทำให้สดชื่นมุมมอง materialized ดังกล่าวเป็นการดำเนินการค่อนข้างหนัก
ดังนั้นฉันจึงยังคงอยู่กับดัชนี GIN จนถึงตอนนี้ด้วยการเพิ่มwork_mem
และลดการสืบค้นหน่วยความจำรอยเท้า