วิธีเพิ่มประสิทธิภาพคิวรีที่ทำงานช้าลงบนลูปซ้อน (Inner Join)


39

TL; DR

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

JOIN table t ON t.member = @value1 OR t.member = @value2 -- this is slow as hell
JOIN table t ON t.member = COALESCE(@value1, @value2)    -- this is blazing fast
-- Note that here if @value1 has a value, @value2 is NULL, and vice versa

ฉันรู้ว่านี่อาจไม่ใช่ปัญหาของทุกคน แต่การเน้นความไวของอนุประโยค ON อาจช่วยให้คุณมองไปในทิศทางที่ถูกต้อง ไม่ว่าในกรณีใดข้อความต้นฉบับอยู่ที่นี่สำหรับนักมานุษยวิทยาในอนาคต:

ข้อความต้นฉบับ

พิจารณาแบบสอบถามแบบง่าย ๆ ต่อไปนี้ (มีเพียง 3 ตารางที่เกี่ยวข้อง)

    SELECT

        l.sku_id AS ProductId,
        l.is_primary AS IsPrimary,
        v1.category_name AS Category1,
        v2.category_name AS Category2,
        v3.category_name AS Category3,
        v4.category_name AS Category4,
        v5.category_name AS Category5

    FROM category c4
    JOIN category_voc v4 ON v4.category_id = c4.category_id and v4.language_code = 'en'

    JOIN category c3 ON c3.category_id = c4.parent_category_id
    JOIN category_voc v3 ON v3.category_id = c3.category_id and v3.language_code = 'en'

    JOIN category c2 ON c2.category_id = c3.category_id
    JOIN category_voc v2 ON v2.category_id = c2.category_id and v2.language_code = 'en'

    JOIN category c1 ON c1.category_id = c2.parent_category_id
    JOIN category_voc v1 ON v1.category_id = c1.category_id and v1.language_code = 'en'

    LEFT OUTER JOIN category c5 ON c5.parent_category_id = c4.category_id
    LEFT OUTER JOIN category_voc v5 ON v5.category_id = c5.category_id and v5.language_code = @lang

    JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
    (
        l.category_id = c4.category_id OR
        l.category_id = c5.category_id
    )

    WHERE c4.[level] = 4 AND c4.version_id = 5

นี่เป็นข้อความค้นหาที่ค่อนข้างง่ายส่วนที่สับสนเพียงอย่างเดียวคือการเข้าร่วมหมวดหมู่สุดท้ายด้วยวิธีนี้เนื่องจากหมวดหมู่ระดับ 5 อาจมีหรือไม่มีอยู่ ในตอนท้ายของการสืบค้นฉันกำลังมองหาข้อมูลหมวดหมู่ต่อรหัสผลิตภัณฑ์ (SKU ID) และนั่นคือสิ่งที่ table_link ของตารางที่มีขนาดใหญ่มากเข้ามาในที่สุดตาราง #Ids เป็นเพียงตารางชั่วคราวที่มี 10'000 Ids

เมื่อดำเนินการฉันจะได้รับแผนการดำเนินการตามจริง:

แผนปฏิบัติการจริง

อย่างที่คุณเห็นเวลาเกือบ 90% ใช้เวลาในวงซ้อนกัน (Inner Join) นี่คือข้อมูลเพิ่มเติมเกี่ยวกับลูปซ้อนกัน:

ลูปซ้อน (Inner Join)

โปรดทราบว่าชื่อตารางไม่ตรงกันทั้งหมดเนื่องจากฉันแก้ไขชื่อตารางแบบสอบถามเพื่อให้สามารถอ่านได้ แต่มันค่อนข้างง่ายที่จะจับคู่ (ads_alt_category = หมวดหมู่) มีวิธีใดที่จะเพิ่มประสิทธิภาพการค้นหานี้หรือไม่ โปรดทราบว่าในการผลิตตาราง temp #Ids ไม่มีอยู่มันเป็นพารามิเตอร์ที่มีมูลค่าของตารางที่มี 10'000 Ids เดียวกันส่งต่อไปยังกระบวนงานที่เก็บไว้

ข้อมูลเพิ่มเติม:

  • ดัชนีหมวดหมู่ใน category_id และ parent_category_id
  • category_voc ดัชนีใน category_id, language_code
  • ดัชนี category_link บน sku_id, category_id

แก้ไข (แก้ไข)

ตามที่ระบุโดยคำตอบที่ยอมรับแล้วปัญหาคือประโยค OR ใน category_link JOIN อย่างไรก็ตามรหัสที่แนะนำในคำตอบที่ยอมรับนั้นช้ามากช้ากว่ารหัสต้นฉบับ วิธีแก้ปัญหาที่เร็วกว่าและสะอาดกว่าคือการแทนที่เงื่อนไข JOIN ปัจจุบันด้วยวิธีต่อไปนี้:

JOIN category_link l on l.sku_id IN (SELECT value FROM @p1) AND l.category_id = COALESCE(c5.category_id, c4.category_id)

การปรับแต่งนาทีนี้เป็นวิธีที่เร็วที่สุดทดสอบกับการเข้าร่วมสองครั้งจากคำตอบที่ยอมรับและทดสอบกับ CROSS APPLY ตามที่แนะนำโดย valverij


เราจะต้องดูแผนการสืบค้นที่เหลือ
RBarryYoung

เพียงหมายเหตุ: เมื่อมีข้อผิดพลาดในการประมาณค่า cardinality ที่คาดว่าจะเกิดขึ้น บ่อยครั้งที่ประสิทธิภาพการสืบค้นเกิดจากการประเมินความสำคัญต่ำ
usr

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

@rbarry หากหลังจากลองวิธีแก้ปัญหาปัจจุบันฉันไม่ได้อะไรฉันจะปรับปรุงคำถาม

1
สิ่งที่เกี่ยวกับการทำซ้ำแบบสอบถามด้วยยูเนี่ยนและกำจัด OR

คำตอบ:


17

ปัญหาดูเหมือนจะอยู่ในส่วนนี้ของรหัส:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

orในการเข้าร่วมเงื่อนไขที่น่าสงสัยอยู่เสมอ หนึ่งข้อเสนอแนะคือการแบ่งออกเป็นสองร่วม:

JOIN category_link l1 on l1.sku_id in (SELECT value FROM #Ids) and l1.category_id = cr.category_id
left outer join
category_link l1 on l2.sku_id in (SELECT value FROM #Ids) and l2.category_id = cr.category_id

จากนั้นคุณต้องแก้ไขแบบสอบถามที่เหลือเพื่อจัดการกับสิ่งนี้ . . coalesce(l1.sku_id, l2.sku_id)เช่นในselectข้อ


ด้วยปริมาณการกรองจะถูกดำเนินการในโดยเฉพาะอย่างยิ่งที่เข้าร่วมฉันยังต้องการทดสอบการเปลี่ยนแปลงJOINไปCROSS APPLYด้วยINการเปลี่ยนไปสู่การEXISTSในAPPLY's WHEREประโยค

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

3
ฉันยอมรับคำตอบนี้เพราะเป็นคำตอบแรกที่ชี้ให้ฉันเห็นปัญหา อย่างไรก็ตามวิธีการแก้ไขที่แนะนำนั้นช้ามากช้ากว่ารหัสต้นฉบับ อย่างไรก็ตามการรู้ว่าประโยค OR เป็นปัญหาเพียงแค่แทนที่ด้วยการON l.category_id = ISNULL(c5.category_id, c4.category_idทำเคล็ดลับ
Luis Ferrao

1
@LuisFerrao . . ขอบคุณสำหรับข้อมูลเพิ่มเติม มันมีประโยชน์ที่จะรู้ว่าcoalesce()เครื่องมือเพิ่มประสิทธิภาพผลักไปในทิศทางที่ถูกต้อง
Gordon Linoff

9

ดังที่ผู้ใช้รายอื่นกล่าวถึงการเข้าร่วมครั้งนี้อาจเป็นสาเหตุ:

JOIN category_link l on l.sku_id IN (SELECT value FROM #Ids) AND
(
    l.category_id = c4.category_id OR
    l.category_id = c5.category_id
)

นอกจากการแบ่งออกเป็นหลายการรวมคุณสามารถลอง CROSS APPLY

CROSS APPLY (
    SELECT [some column(s)]
    FROM category_link x
    WHERE EXISTS(SELECT value FROM #Ids WHERE value = x.sku_id)
    AND (x.category_id = c4.category_id OR x.category_id = c5.category_id)        
) l

จากลิงก์ MSDN ด้านบน:

ฟังก์ชันที่มีค่าเป็นตารางทำหน้าที่เป็นอินพุตที่ถูกต้องและนิพจน์ของตารางด้านนอกทำหน้าที่เป็นอินพุตซ้าย การป้อนข้อมูลที่ถูกต้องคือการประเมินของแต่ละแถวจากการป้อนข้อมูลทางด้านซ้ายและแถวที่ผลิตมีการทำงานร่วมกันเพื่อผลลัพธ์สุดท้าย

โดยทั่วไปแล้วAPPLYก็เหมือนกับแบบสอบถามย่อยที่กรองระเบียนทางขวาก่อนจากนั้นจึงนำไปใช้กับแบบสอบถามที่เหลือของคุณ

บทความนี้ทำงานได้ดีมากในการอธิบายว่ามันคืออะไรและใช้งานเมื่อใด: http://explainextended.com/2009/07/16/inner-join-vs-cross-apply/

อย่างไรก็ตามสิ่งสำคัญคือต้องทราบว่าการดำเนินการCROSS APPLYนั้นอาจไม่เร็วกว่าINNER JOINเสมอไป ในหลาย ๆ สถานการณ์มันอาจจะเหมือนกัน แต่ในบางกรณีฉันได้เห็นมันช้าลง (อีกครั้งทั้งหมดนี้ขึ้นอยู่กับโครงสร้างตารางของคุณและคิวรีเอง)

ตามกฎทั่วไปของหัวแม่มือหากฉันพบว่าตัวเองเข้าร่วมกับตารางที่มีข้อความที่มีเงื่อนไขมากเกินไปฉันก็มักจะเอนไปทาง APPLY

นอกจากนี้ข้อควรทราบ: OUTER APPLYจะทำหน้าที่เหมือนLEFT JOIN

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


ฉันทดสอบวิธีนี้อย่างถี่ถ้วน ในขณะที่คุณเขียนมันค่อนข้างช้า แต่คุณลืมที่จะใช้คำแนะนำที่คุณเริ่มต้นข้อความของคุณด้วย การเปลี่ยนAND x.cat = c4.cat OR x.cat = c5.catจากx.cat = ISNULL(c5.cat, c4.cat)และการกำจัดของประโยคหนึ่งในที่ทำนี้วิธีที่เร็วที่สุดที่สองและที่คุ้มค่าของ upvote เพราะมันมีข้อมูลที่สวย
Luis Ferrao

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