ทำไม PostgreSQL จึงเลือกการเข้าร่วมที่มีราคาแพงกว่า?


13

PostgreSQL ใช้ค่าเริ่มต้นบวก

default_statistics_target=1000
random_page_cost=1.5

รุ่น

PostgreSQL 10.4 on x86_64-pc-linux-musl, compiled by gcc (Alpine 6.4.0) 6.4.0, 64-bit

ฉันดูดและวิเคราะห์แล้ว แบบสอบถามตรงไปตรงมามาก:

SELECT r.price
FROM account_payer ap
  JOIN account_contract ac ON ap.id = ac.account_payer_id
  JOIN account_schedule "as" ON ac.id = "as".account_contract_id
  JOIN schedule s ON "as".id = s.account_schedule_id
  JOIN rate r ON s.id = r.schedule_id
WHERE ap.account_id = 8

ทุกidคอลัมน์เป็นคีย์หลักและทุกสิ่งที่เข้าร่วมเป็นความสัมพันธ์ของคีย์ภายนอกและคีย์ต่างประเทศแต่ละรายการจะมีดัชนี account_payer.account_idบวกกับดัชนี

ใช้เวลา 3.93 วินาทีเพื่อส่งคืนแถว 76k

Merge Join  (cost=8.06..83114.08 rows=3458267 width=6) (actual time=0.228..3920.472 rows=75548 loops=1)
  Merge Cond: (s.account_schedule_id = "as".id)
  ->  Nested Loop  (cost=0.57..280520.54 rows=6602146 width=14) (actual time=0.163..3756.082 rows=448173 loops=1)
        ->  Index Scan using schedule_account_schedule_id_idx on schedule s  (cost=0.14..10.67 rows=441 width=16) (actual time=0.035..0.211 rows=89 loops=1)
        ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.025..39.903 rows=5036 loops=89)
              Index Cond: (schedule_id = s.id)
  ->  Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)
        ->  Nested Loop  (cost=0.43..49.32 rows=55 width=8) (actual time=0.048..1.110 rows=66 loops=1)
              ->  Nested Loop  (cost=0.29..27.46 rows=105 width=16) (actual time=0.030..0.616 rows=105 loops=1)
                    ->  Index Scan using account_schedule_pkey on account_schedule "as"  (cost=0.14..6.22 rows=105 width=16) (actual time=0.014..0.098 rows=105 loops=1)
                    ->  Index Scan using account_contract_pkey on account_contract ac  (cost=0.14..0.20 rows=1 width=16) (actual time=0.003..0.003 rows=1 loops=105)
                          Index Cond: (id = "as".account_contract_id)
              ->  Index Scan using account_payer_pkey on account_payer ap  (cost=0.14..0.21 rows=1 width=8) (actual time=0.003..0.003 rows=1 loops=105)
                    Index Cond: (id = ac.account_payer_id)
                    Filter: (account_id = 8)
                    Rows Removed by Filter: 0
Planning time: 5.843 ms
Execution time: 3929.317 ms

ถ้าฉันตั้งค่าjoin_collapse_limit=1มันใช้เวลา 0.16 วินาทีความเร็ว 25x

Nested Loop  (cost=6.32..147323.97 rows=3458267 width=6) (actual time=8.908..151.860 rows=75548 loops=1)
  ->  Nested Loop  (cost=5.89..390.23 rows=231 width=8) (actual time=8.730..11.655 rows=66 loops=1)
        Join Filter: ("as".id = s.account_schedule_id)
        Rows Removed by Join Filter: 29040
        ->  Index Scan using schedule_pkey on schedule s  (cost=0.27..17.65 rows=441 width=16) (actual time=0.014..0.314 rows=441 loops=1)
        ->  Materialize  (cost=5.62..8.88 rows=55 width=8) (actual time=0.001..0.011 rows=66 loops=441)
              ->  Hash Join  (cost=5.62..8.61 rows=55 width=8) (actual time=0.240..0.309 rows=66 loops=1)
                    Hash Cond: ("as".account_contract_id = ac.id)
                    ->  Seq Scan on account_schedule "as"  (cost=0.00..2.05 rows=105 width=16) (actual time=0.010..0.028 rows=105 loops=1)
                    ->  Hash  (cost=5.02..5.02 rows=48 width=8) (actual time=0.178..0.178 rows=61 loops=1)
                          Buckets: 1024  Batches: 1  Memory Usage: 11kB
                          ->  Hash Join  (cost=1.98..5.02 rows=48 width=8) (actual time=0.082..0.143 rows=61 loops=1)
                                Hash Cond: (ac.account_payer_id = ap.id)
                                ->  Seq Scan on account_contract ac  (cost=0.00..1.91 rows=91 width=16) (actual time=0.007..0.023 rows=91 loops=1)
                                ->  Hash  (cost=1.64..1.64 rows=27 width=8) (actual time=0.048..0.048 rows=27 loops=1)
                                      Buckets: 1024  Batches: 1  Memory Usage: 10kB
                                      ->  Seq Scan on account_payer ap  (cost=0.00..1.64 rows=27 width=8) (actual time=0.009..0.023 rows=27 loops=1)
                                            Filter: (account_id = 8)
                                            Rows Removed by Filter: 24
  ->  Index Scan using rate_schedule_id_code_modifier_facility_idx on rate r  (cost=0.43..486.03 rows=15005 width=10) (actual time=0.018..1.685 rows=1145 loops=66)
        Index Cond: (schedule_id = s.id)
Planning time: 4.692 ms
Execution time: 160.585 ms

ผลลัพธ์เหล่านี้มีเหตุผลเล็กน้อยสำหรับฉัน สิ่งแรกมีค่าใช้จ่าย (สูงมาก) ที่ 280,500 สำหรับการวนซ้ำซ้อนกันสำหรับดัชนีกำหนดเวลาและอัตรา เหตุใด PostgreSQL จึงเลือกการเข้าร่วมที่แพงมากเป็นอันดับแรก

ข้อมูลเพิ่มเติมที่ร้องขอผ่านความคิดเห็น

คือrate_schedule_id_code_modifier_facility_idxดัชนีสารประกอบหรือไม่?

มันคือschedule_idการเป็นคอลัมน์แรก ฉันได้ทำให้มันเป็นดัชนีเฉพาะและมันถูกเลือกโดยผู้วางแผนคิวรี แต่มันไม่ได้ส่งผลกระทบต่อประสิทธิภาพการทำงานหรือส่งผลกระทบต่อแผน


คุณสามารถเปลี่ยนการตั้งค่าdefault_statistics_targetและrandom_page_costกลับไปเป็นค่าเริ่มต้นได้หรือไม่ จะเกิดอะไรขึ้นเมื่อคุณเพิ่มขึ้นdefault_statistics_targetอีก คุณสามารถสร้าง DB Fiddle (ที่ dbfiddle.uk) และพยายามสร้างปัญหาขึ้นมาใหม่ได้หรือไม่?
โคลิน 't ฮาร์ต

3
คุณสามารถตรวจสอบสถิติที่เกิดขึ้นจริงเพื่อดูว่ามีบางสิ่งที่บิดเบือน / แปลกเกี่ยวกับข้อมูลของคุณหรือไม่? postgresql.org/docs/10/static/planner-stats.html
Colin 't Hart

ค่าปัจจุบันของพารามิเตอร์work_memคืออะไร การเปลี่ยนให้เวลาต่างกันหรือไม่
eppesuig

คำตอบ:


1

ดูเหมือนว่าทั้งสถิติของคุณไม่ถูกต้อง (วิ่งฝุ่นวิเคราะห์เพื่อฟื้นฟูพวกเขา) อย่างใดอย่างหนึ่งที่คุณได้มีความสัมพันธ์คอลัมน์ในรูปแบบของคุณ (และดังนั้นคุณจะต้องดำเนินการcreate statisticsแจ้งให้ทราบกบของความจริงที่ว่า)

join_collapseพารามิเตอร์ช่วยให้วางแผนเพื่อจัดเรียงร่วมเพื่อให้การดำเนินการเป็นครั้งแรกหนึ่งที่เรียกข้อมูลน้อย แต่เพื่อประสิทธิภาพเราไม่สามารถให้นักวางแผนทำสิ่งนั้นกับแบบสอบถามที่มีผู้เข้าร่วมจำนวนมาก โดยค่าเริ่มต้นมันถูกตั้งค่าเป็น 8 รวมสูงสุด ด้วยการตั้งค่าเป็น 1 คุณเพียงปิดการใช้งานความสามารถนั้น

ดังนั้น postgres เล็งเห็นว่ามีจำนวนแถวที่แบบสอบถามควรดึงข้อมูลได้อย่างไร มันใช้สถิติเพื่อประมาณจำนวนแถว

สิ่งที่เราสามารถเห็นได้ในแผนการอธิบายของคุณคือมีการประมาณจำนวนแถวที่ไม่ถูกต้องหลายค่า (ค่าแรกคือค่าประมาณค่าที่สองเป็นค่าจริง)

ตัวอย่างเช่นที่นี่:

Materialize  (cost=0.43..49.46 rows=55 width=8) (actual time=0.060..12.984 rows=74697 loops=1)

ผู้วางแผนประมาณว่าจะได้ 55 แถวเมื่อเขาได้รับจริง 74697

สิ่งที่ฉันทำ (ถ้าฉันอยู่ในรองเท้าของคุณ) คือ:

  • analyze ห้าตารางที่เกี่ยวข้องกับการรีเฟรชสถิติ
  • แข่งใหม่ explain analyze
  • ดูความแตกต่างระหว่างหมายเลขแถวโดยประมาณกับหมายเลขแถวจริง
  • หากหมายเลขแถวประมาณการถูกต้องอาจมีการเปลี่ยนแปลงแผนและมีประสิทธิภาพมากขึ้น หากทุกอย่างเรียบร้อยคุณอาจลองเปลี่ยนการตั้งค่าอัตโนมัติเพื่อวิเคราะห์ (และสูญญากาศ) ทำงานบ่อยขึ้น
  • หากหมายเลขแถวโดยประมาณยังคงไม่ถูกต้องดูเหมือนว่าคุณมีข้อมูลที่สัมพันธ์กันในตารางของคุณ (การละเมิดฟอร์มปกติครั้งที่สาม) คุณอาจพิจารณาประกาศด้วยCREATE STATISTICS(เอกสารประกอบที่นี่ )

หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับการประมาณแถวและการคำนวณคุณจะพบทุกสิ่งที่คุณต้องการในการสนทนา conf ของ Tomas Vondra "สร้างสถิติ - มีไว้เพื่ออะไร" (สไลด์ที่นี่ )

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.