แบบสอบถาม 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 จากนั้นใส่รายการผลลัพธ์ลงในคิวรี แม้จะมีหมายเลข 5000 ในรายการ IN () มันเร็วกว่าโซลูชันที่สองถึงสี่เท่า อย่างไรก็ตามดูเหมือนว่าผิด (เช่นอาจเร็วขึ้น 100 เท่า :)) มันไม่สามารถเข้าใจได้อย่างสมบูรณ์ว่าเหตุใดตัววางแผนคิวรีจึงใช้วิธีการที่แตกต่างไปจากเดิมอย่างสิ้นเชิงสำหรับเคียวรีสองรายการนี้ดังนั้นฉันจึงต้องการหาวิธีแก้ปัญหาที่ดีกว่าสำหรับปัญหานี้


คุณสามารถเขียนรหัสของคุณอีกครั้งเพื่อให้จำศีลสร้างJOINแทนที่จะIN ()? นอกจากนี้ยังได้publicationรับการวิเคราะห์เมื่อเร็ว ๆ นี้?
dezso

ใช่ฉันทำทั้ง VACUUM ANALYZE และ VACUUM FULL แล้ว ไม่มีการเปลี่ยนแปลงประสิทธิภาพ ในส่วนที่สอง AFAIR เราลองและไม่ส่งผลกระทบต่อประสิทธิภาพการค้นหาอย่างมีนัยสำคัญ
P.Péter

1
หาก Hibernate ล้มเหลวในการสร้างแบบสอบถามที่เหมาะสมทำไมคุณไม่ใช้ raw SQL? นั่นเหมือนกับการยืนยันใน Google แปลขณะที่คุณรู้วิธีแสดงภาษาอังกฤษได้ดีขึ้นแล้ว ในฐานะที่เป็นคำถามของคุณ: (SELECT 9762715)จริงๆมันขึ้นอยู่กับการค้นหาที่ซ่อนอยู่หลังที่เกิดขึ้นจริง
Erwin Brandstetter

ขณะที่ผมกล่าวถึงข้างล่างมันช้าแม้ว่าแบบสอบถามภายในคือ (SELECT 9762715)สำหรับคำถามไฮเบอร์เนต: สามารถทำได้ แต่ต้องมีการเขียนโค้ดอย่างจริงจังเนื่องจากเรามีเกณฑ์การจำศีลไฮเบอร์เนตที่ผู้ใช้กำหนดซึ่งได้รับการแปลแบบทันที ดังนั้นโดยพื้นฐานแล้วเราจะปรับเปลี่ยนโหมดไฮเบอร์เนตซึ่งเป็นงานที่ใหญ่และมีผลข้างเคียงมากมาย
P.Péter

คำตอบ:


6

แก่นของปัญหาชัดเจนที่นี่:

Seq สแกนบนสิ่งพิมพ์ (ราคา = 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มีขนาดเล็กเพียงพอ Postgres จะสลับไปที่การสแกนดัชนี (บิตแมป) อย่างไรก็ตามนั่นใช้ได้กับกรณีทั่วไปเท่านั้น หยุดทำงานเมื่อเพิ่มORเงื่อนไข: ตัววางแผนคิวรีไม่สามารถรับมือกับสิ่งนั้นได้ในขณะนี้

ฉันไม่ค่อยใช้IN (SELECT ...)เพื่อเริ่มต้นด้วย โดยทั่วไปแล้วจะมีวิธีที่ดีกว่าในการใช้งานแบบเดียวกันโดยมีการEXISTSเข้าร่วมกึ่ง บางครั้งด้วย ( LEFT) JOIN( LATERAL) ...

วิธีแก้ปัญหาที่ชัดเจนจะใช้UNIONแต่คุณตัดออก ฉันไม่สามารถพูดได้มากกว่านี้หากไม่ทราบถึงข้อความค้นหาย่อยจริงและรายละเอียดอื่น ๆ ที่เกี่ยวข้อง


2
มีแบบสอบถามไม่มีที่ซ่อนอยู่หลัง (SELECT 9762715) ! ถ้าฉันเรียกใช้แบบสอบถามที่แน่นอนที่คุณเห็นด้านบน แน่นอนคิวรีแบบจำศีลดั้งเดิมนั้นซับซ้อนกว่าเล็กน้อย แต่ฉัน (ฉันคิดว่า) จัดการเพื่อระบุตำแหน่งที่ผู้วางแผนคิวรีหลงทางดังนั้นฉันจึงแสดงส่วนของแบบสอบถามนั้น อย่างไรก็ตามคำอธิบายข้างต้นและแบบสอบถามเป็น verbatim ctrl-cv
P.Péter

สำหรับส่วนที่สองขีด จำกัด ภายในใช้งานไม่ได้: 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.Péter

1
@ P.Péter: ฉันวิ่งทดสอบอีกครั้งและตระหนักว่าฉันได้ทดสอบโดยไม่มีORเงื่อนไข เคล็ดลับที่LIMITใช้ได้กับเคสที่ง่ายกว่าเท่านั้น
Erwin Brandstetter

6

เพื่อนร่วมงานของฉันพบวิธีในการเปลี่ยนคิวรีเพื่อให้ต้องการเขียนใหม่อย่างง่ายและทำสิ่งที่ต้องทำคือทำการเลือกย่อยในขั้นตอนเดียวจากนั้นดำเนินการเพิ่มเติมเกี่ยวกับผลลัพธ์:

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

การวิเคราะห์คำอธิบายตอนนี้คือ:

 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

ดูเหมือนว่าเราสามารถสร้าง parser ง่าย ๆ ที่ค้นหาและเขียนการเลือกย่อยทั้งหมดด้วยวิธีนี้และเพิ่มลงใน hook hibernate เพื่อจัดการเคียวรีเนทีฟ


เสียงสนุก. มันง่ายกว่าไหมที่จะลบสิ่งต่าง ๆ ทั้งหมดSELECTอย่างที่คุณมีในแบบสอบถามแรกของคุณในคำถาม?
dezso

แน่นอนฉันจะทำวิธีการขั้นตอนที่สอง: ทำแยกกันแล้วทำนอกเลือกกับรายการคงหลังจากที่SELECT INอย่างไรก็ตามนั่นช้ากว่าอย่างมาก (5-10 ครั้งหากแบบสอบถามย่อยมีผลลัพธ์มากกว่าสองสามรายการ) เนื่องจากคุณมีเครือข่ายพิเศษไปกลับและคุณได้จัดรูปแบบผลลัพธ์จำนวนมากจากนั้นจาวาแยกวิเคราะห์ผลลัพธ์เหล่านั้น (จากนั้นทำ ย้อนกลับเหมือนเดิมอีกครั้ง) วิธีการแก้ปัญหาข้างต้นทำแบบเดียวกันในเชิงความหมายในขณะที่ออกจากกระบวนการภายใน postgres โดยรวมแล้วปัจจุบันนี้ดูเหมือนจะเป็นวิธีที่เร็วที่สุดด้วยการปรับเปลี่ยนที่เล็กที่สุดในกรณีของเรา
P.Péter

อ่าฉันเข้าใจแล้ว สิ่งที่ฉันไม่รู้ก็คือคุณสามารถรับ 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.