เหตุใด CTE จึงเลวร้ายยิ่งกว่าข้อความค้นหาย่อยแบบอินไลน์


11

ฉันพยายามเข้าใจวิธีการทำงานของตัววางแผนคิวรีใน postgresql

ฉันมีคำถามนี้:

select id from users 
    where id <> 2
    and gender = (select gender from users where id = 2)
    order by latest_location::geometry <-> (select latest_location from users where id = 2) ASC
    limit 50

มันทำงานในเวลาน้อยกว่า 10ms ในฐานข้อมูลของฉันด้วยรายการประมาณ 500k ในตารางผู้ใช้

จากนั้นฉันคิดว่าเพื่อหลีกเลี่ยงการเลือกย่อยที่ซ้ำกันฉันสามารถเขียนแบบสอบถามเป็น CTE อีกครั้งเช่นนี้

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

อย่างไรก็ตามข้อความค้นหาที่เขียนใหม่นี้จะทำงานในเวลาประมาณ 1 วินาที! ทำไมสิ่งนี้ถึงเกิดขึ้น ฉันเห็นในคำอธิบายว่ามันไม่ได้ใช้ดัชนีเรขาคณิต แต่สามารถทำอะไรได้บ้าง ขอบคุณ!

อีกวิธีในการเขียนแบบสอบถามคือ:

select u.id, u.popularity from users u, (select gender, latest_location from users where id = 2) as me 
    where u.gender = me.gender
    order by  u.latest_location::geometry <-> me.latest_location::geometry ASC
    limit 50;

อย่างไรก็ตามสิ่งนี้จะช้าเท่ากับ CTE

หากในอีกทางหนึ่งฉันแยกพารามิเตอร์ me ออกและแทรกแบบสอบถามแบบคงที่อีกครั้งอย่างรวดเร็ว:

select u.id, u.popularity from users u
    where u.gender = 'male'
    order by  u.latest_location::geometry <-> '0101000000A49DE61DA71C5A403D0AD7A370F54340'::geometry ASC
    limit 50;

อธิบายแบบสอบถามแรก (เร็ว)

 Limit  (cost=5.69..20.11 rows=50 width=36) (actual time=0.512..8.114 rows=50 loops=1)
   InitPlan 1 (returns $0)
     ->  Index Scan using users_pkey on users users_1  (cost=0.42..2.64 rows=1 width=32) (actual time=0.032..0.033 rows=1 loops=1)
           Index Cond: (id = 2)
   InitPlan 2 (returns $1)
     ->  Index Scan using users_pkey on users users_2  (cost=0.42..2.64 rows=1 width=4) (actual time=0.009..0.010 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Index Scan using users_latest_location_gix on users  (cost=0.41..70796.51 rows=245470 width=36) (actual time=0.509..8.100 rows=50 loops=1)
         Order By: (latest_location <-> $0)
         Filter: (gender = $1)
         Rows Removed by Filter: 20
 Total runtime: 8.211 ms
(12 rows)

อธิบายแบบสอบถามที่สอง (ช้า)

Limit  (cost=62419.82..62419.95 rows=50 width=76) (actual time=1024.963..1024.970 rows=50 loops=1)
   CTE me
     ->  Index Scan using users_pkey on users  (cost=0.42..2.64 rows=1 width=221) (actual time=0.037..0.038 rows=1 loops=1)
           Index Cond: (id = 2)
   ->  Sort  (cost=62417.18..63030.86 rows=245470 width=76) (actual time=1024.959..1024.963 rows=50 loops=1)
         Sort Key: ((u.latest_location <-> me.latest_location))
         Sort Method: top-N heapsort  Memory: 28kB
         ->  Hash Join  (cost=0.03..54262.85 rows=245470 width=76) (actual time=0.122..938.131 rows=288646 loops=1)
               Hash Cond: (u.gender = me.gender)
               ->  Seq Scan on users u  (cost=0.00..49353.41 rows=490941 width=48) (actual time=0.021..465.025 rows=490994 loops=1)
               ->  Hash  (cost=0.02..0.02 rows=1 width=36) (actual time=0.054..0.054 rows=1 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  CTE Scan on me  (cost=0.00..0.02 rows=1 width=36) (actual time=0.047..0.049 rows=1 loops=1)
 Total runtime: 1025.096 ms

3
ฉันเขียนเกี่ยวกับเรื่องนี้เมื่อเร็ว ๆ นี้; ดูblog.2ndquadrant.com/postgresql-ctes-are-optimization-fences แม้ว่าในปัจจุบันจะมีปัญหา DNS บางอย่างที่อาจ จำกัด การเข้าถึงของไซต์นั้น ลองใช้แบบสอบถามย่อยFROMแทนคำ CTE เพื่อผลลัพธ์ที่ดีที่สุด
Craig Ringer

เกิดอะไรขึ้นถ้าคุณใช้(select id, latest_location from users where id = 2)เป็น cte อาจเป็น * ที่ทำให้เกิดปัญหานี้
cha

ผมจะมีความคิดที่คุณต้องการดูสำหรับผู้ใช้ที่ใกล้เคียงที่สุดของเพศตรงข้าม :)
ชะอำ

@cha ทำให้ไม่มีความแตกต่างของความเร็วในการเลือกเพศและตำแหน่งใน cte (ในกรณีของฉันฉันต้องการที่จะใช้ค่าเฉลี่ยของผู้ใช้ที่คล้ายกันเพียงแค่ว่าฉันง่ายแบบสอบถามสำหรับคำถาม)
viblo

@ CraigRinger ฉันไม่คิดว่ามันเป็นรั้วการเพิ่มประสิทธิภาพ ฉันลองทำตามคำแนะนำของคุณแล้วมันก็ช้าเช่นกัน ในทางกลับกันถ้าฉันแยกพารามิเตอร์ด้วยตนเองมันเป็นแบบรวดเร็ว (และเป็นตัวเลือกที่แท้จริงในกรณีของฉันผลลัพธ์สุดท้ายคือฟังก์ชั่นต่อไป)
viblo

คำตอบ:


11

ลองสิ่งนี้:

with me as (
    select * from users where id = 2
)
select u.id, u.popularity from users u, me 
    where u.gender = (select gender from me)
    order by  u.latest_location::geometry <-> (select latest_location from me)::geometry ASC
    limit 50;

เมื่อฉันดูแผนรวดเร็วนี่คือสิ่งที่กระโดดออกมาที่ฉัน (ตัวหนา):

 ขีด จำกัด (ค่า = 5.69..20.11 แถว = 50 ความกว้าง = 36) (เวลาจริง = 0.512..8.114 แถว = 50 ลูป = 1)
   InitPlan 1 ( ส่งคืน $ 0 )
     -> ดัชนีการสแกนโดยใช้ users_pkey กับผู้ใช้ users_1 (ราคา = 0.42..2.64 แถว = 1 ความกว้าง = 32) (เวลาจริง = 0.032..0.033 แถว = 1 ลูป = 1)
           ดัชนี Cond: (id = 2)
   InitPlan 2 ( ส่งคืน $ 1 )
     -> ดัชนีการสแกนโดยใช้ users_pkey กับผู้ใช้ users_2 (ราคา = 0.42..2.64 แถว = 1 ความกว้าง = 4) (เวลาจริง = 0.009..0.010 แถว = 1 ลูป = 1)
           ดัชนี Cond: (id = 2)
   -> ดัชนีการสแกนโดยใช้ users_latest_location_gix กับผู้ใช้ (ราคา = 0.41..70796.51 แถว = 245470 กว้าง = 36) (เวลาจริง = 0.509..8.100 แถว = 50 ลูป = 1)
         สั่งซื้อโดย: (latest_location   $ 0 )
         ตัวกรอง: (gender = $ 1 )
         แถวถูกลบโดยตัวกรอง: 20
 รันไทม์ทั้งหมด: 8.211 ms
(12 แถว)

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


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