ทำไมการเพิ่ม TOP 1 ถึงทำให้ประสิทธิภาพแย่ลงอย่างเห็นได้ชัด?


39

ฉันมีคำถามที่ค่อนข้างง่าย

SELECT TOP 1 dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

นั่นทำให้ฉันมีประสิทธิภาพที่น่ากลัว (อย่างที่ไม่เคยใส่ใจที่จะรอให้มันจบ) แผนแบบสอบถามมีลักษณะดังนี้:

ป้อนคำอธิบายรูปภาพที่นี่

อย่างไรก็ตามถ้าฉันลบTOP 1แผนการที่มีลักษณะเช่นนี้ออกและทำงานใน 1-2 วินาที:

ป้อนคำอธิบายรูปภาพที่นี่

แก้ไข PK & การทำดัชนีด้านล่าง

ความจริงที่ว่าTOP 1แผนการสืบค้นที่เปลี่ยนแปลงนั้นไม่ได้ทำให้ฉันแปลกใจ แต่ฉันก็แปลกใจนิดหน่อยที่มันทำให้แย่ลงไปอีกมาก

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

แก้ไขสำหรับผู้ที่อ่านสิ่งนี้หลังจากข้อเท็จจริงที่นี่มีข้อมูลเพิ่มเติมอีกสองสามชิ้น

  • Document_Queue - PK / CI คือ D_ID และมีแถว ~ 5k
  • Correspondence_Journal - PK / CI คือ FILE_NUMBER, CORRESPONDENCE_ID และมีแถวประมาณ 1.4 ล้านแถว

เมื่อฉันเริ่มไม่มีดัชนีอื่น ๆ ฉันได้รับหนึ่งใน Correspondence_Journal (Document_Id, File_Number)


1
คุณมีข้อ จำกัด คีย์ต่างประเทศที่บังคับใช้DOCUMENT_IDความสัมพันธ์ระหว่างสองตาราง (หรือว่าทุกระเบียนCORRESPONDENCE_JOURNALมีระเบียนที่ตรงกันDOCUMENT_QUEUEหรือไม่)
Daniel Hutmacher

คำตอบ:


28

ลองบังคับให้แฮชเข้าร่วม *

SELECT TOP 1 
       dc.DOCUMENT_ID,
       dc.COPIES,
       dc.REQUESTOR,
       dc.D_ID,
       cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
INNER HASH JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
       AND dc.QUEUE_DATE <= GETDATE()
       AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER

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


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

สารสกัด BOL

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

OPTION (HASH JOIN) แบบสอบถามFORCE ORDERคำใบ้อาจจะล่วงล้ำน้อยกว่าในกรณีที่เหมาะสมตั้งแต่นี้ไม่ได้หมายความว่า อย่างไรก็ตามจะมีผลกับการรวมทั้งหมดในแบบสอบถาม โซลูชั่นอื่น ๆ ที่มีอยู่


1
ดูเหมือนว่าคำตอบที่ถูกต้องและความแตกต่างเพียงอย่างเดียวระหว่างมันกับแผนที่ง่ายกว่าคือการเรียงลำดับเพิ่มเติมที่ด้านหน้า
Kenneth Fisher

3
ไม่แน่ใจว่าฉันชอบคำตอบนี้ คำแนะนำการเข้าร่วมมีการบุกรุกมาก ควรทำการเปลี่ยนแปลงการทำดัชนีอย่างง่าย ๆ ก่อนเช่นดัชนีในคอลัมน์วันที่
usr

@usr มันคือการเข้าร่วม PK แบบง่ายที่ทำงานในเวลาน้อยกว่าหนึ่งวินาที เดิมพันที่ปลอดภัยสวยที่นี่
paparazzo

4
ในการบังคับให้เข้าร่วมแฮชคุณกำลังสแกนตารางขนาดใหญ่ มีตัวเลือกที่ดีกว่า
Rob Farley

30

เมื่อคุณได้รับแผนที่ถูกต้องด้วยORDER BYคุณอาจจะแค่หมุนTOPผู้ให้บริการของคุณเอง?

SELECT DOCUMENT_ID, COPIES, REQUESTOR, D_ID, FILE_NUMBER
FROM (
    SELECT dc.DOCUMENT_ID,
           dc.COPIES,
           dc.REQUESTOR,
           dc.D_ID,
           cj.FILE_NUMBER,
           ROW_NUMBER() OVER (ORDER BY cj.FILE_NUMBER) AS _rownum
    FROM DOCUMENT_QUEUE dc
    INNER JOIN CORRESPONDENCE_JOURNAL cj
        ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
    WHERE dc.QUEUE_DATE <= GETDATE()
      AND dc.PRINT_LOCATION = 2
) AS sub
WHERE _rownum=1;

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


3
ในขณะที่มันให้โอเปอเรเตอร์ด้านบน (และอีกหลายสิ่ง (โครงการลำดับ, เซกเมนต์, และการจัดเรียง)) มันยังคงทำงานอยู่ที่รอง ฉันจะให้คำตอบที่ถูกต้องกับ @frisbee แม้ว่าเขาจะเป็นคนแรกและมันก็ง่ายกว่า คำตอบที่ดีแม้ว่า
Kenneth Fisher

10
@ เคนเน็ ธ ฟิชเชอร์คำตอบของจานร่อนนั้นง่ายกว่า แต่ในทางที่ค้อนขนาดใหญ่ตอกตะปูเสร็จมากกว่าค้อนตีกรอบทั่วไป นอกจากนี้ยังมีความเสี่ยงสูงโดยเฉพาะถ้าทิ้งไว้ในระยะทางไกล ฉันจะไม่ใช้คำแนะนำอย่างนั้นยกเว้นในการทดสอบหรืออาจเป็นข้อยกเว้น
Steve Mangiameli

@SteveMangiameli ในกรณีนี้มีเพียงคนเดียวเท่านั้นที่เข้าร่วมเพื่อให้ข้อกังวลจำนวนหนึ่งหายไป ฉันตระหนักถึงความเสี่ยงของการใช้คำใบ้เข้าร่วม (หรือคำใบ้คำค้นหา) ฉันแค่คิดว่ามันเป็นธรรมในกรณีนี้
Kenneth Fisher

5
@KennethFisher Imo ความเสี่ยงหลักของคำแนะนำการสืบค้นคือเมื่อข้อมูลของคุณเติบโตหรือเปลี่ยนแปลงแผนแบบสอบถามที่คุณบังคับใช้อาจเลวร้ายยิ่งกว่าที่ระบบจะพบได้ด้วยตัวเอง คุณได้เห็นแล้วว่าความผิดพลาดเล็กน้อยในแผนสามารถส่งผลกระทบต่อประสิทธิภาพได้อย่างจริงจัง การใช้คำใบ้ในการผลิตกำลังประกาศว่า "ฉันรู้ว่าแผนนี้จะเป็นสิ่งที่ดีที่สุดเสมอเพราะฉันเข้าใจการวางแผนและข้อมูลของฉันจะมีผลตลอดอายุการใช้งานของแบบสอบถามนี้ในการผลิต" ฉันไม่เคยมั่นใจเรื่องแบบสอบถามเลย
jpmc26

29

แก้ไข: +1 ทำงานในสถานการณ์นี้เนื่องจากปรากฎว่าFILE_NUMBERเป็นสตริงจำนวนเต็มศูนย์ของสตริง ทางออกที่ดีที่นี่สำหรับสตริงคือการผนวก''(สตริงว่างเปล่า) เช่นการผนวกค่าจะมีผลต่อการสั่งซื้อหรือหมายเลขที่จะเพิ่มอะไรบางอย่างที่เป็นค่าคงที่ sign(rand()+1)แต่มีฟังก์ชั่นที่ไม่ได้กำหนดเช่น แนวคิดของ 'การแบ่งเรียงลำดับ' ยังคงใช้ได้ที่นี่เป็นเพียงวิธีการของฉันไม่เหมาะ

+1

ไม่ฉันไม่ได้หมายความว่าฉันเห็นด้วยกับอะไรฉันก็หมายความว่าเป็นวิธีแก้ปัญหา หากคุณเปลี่ยนการค้นหาของคุณเป็นแบบORDER BY cj.FILE_NUMBER + 1นั้นTOP 1จะทำงานต่างออกไป

คุณจะเห็นด้วยเป้าหมายแถวเล็ก ๆ สำหรับการสืบค้นที่เรียงลำดับระบบจะพยายามใช้ข้อมูลตามลำดับเพื่อหลีกเลี่ยงการมีตัวดำเนินการเรียงลำดับ นอกจากนี้ยังจะหลีกเลี่ยงการสร้างตารางแฮชโดยคิดว่าอาจไม่ต้องทำงานมากเกินไปเพื่อค้นหาแถวแรก ในกรณีของคุณสิ่งนี้เป็นความผิด - จากความหนาของลูกศรเหล่านั้นดูเหมือนว่าต้องใช้ข้อมูลจำนวนมากเพื่อค้นหาคู่ที่ตรงกัน

ความหนาของลูกศรเหล่านั้นแสดงว่าDOCUMENT_QUEUEตาราง (DQ) ของคุณนั้นเล็กกว่าCORRESPONDENCE_JOURNALโต๊ะ (CJ) ของคุณมาก และแผนการที่ดีที่สุดคือการตรวจสอบแถว DQ จนกว่าจะพบแถว CJ ที่จริงแล้วนั่นคือสิ่งที่เครื่องมือเพิ่มประสิทธิภาพ Query (QO) จะทำถ้ามันไม่มีสิ่งที่น่ารำคาญORDER BYในนั้นนั่นได้รับการสนับสนุนอย่างดีจากดัชนีครอบคลุมใน CJ

ดังนั้นหากคุณORDER BYทำสิ่งที่ตกต่ำลงอย่างสมบูรณ์ฉันคาดว่าคุณจะได้รับแผนการที่เกี่ยวข้องกับ Nested Loop วนซ้ำแถวใน DQ ค้นหา CJ เพื่อให้แน่ใจว่าแถวนั้นมีอยู่ และด้วยTOP 1สิ่งนี้จะหยุดลงหลังจากถูกดึงแถวเดียว

แต่ถ้าคุณต้องการแถวแรกFILE_NUMBERตามลำดับคุณสามารถหลอกให้ระบบละเลยดัชนีที่ดูเหมือนว่า (ไม่ถูกต้อง) ว่ามีประโยชน์มากโดยทำORDER BY CJ.FILE_NUMBER+1- ซึ่งเรารู้ว่าจะรักษาลำดับเดิมเหมือนเดิม แต่สำคัญ QO ไม่ QO จะมุ่งเน้นไปที่การตั้งค่าทั้งหมดเพื่อให้ผู้ปฏิบัติงานสามารถจัดเรียง Top N Sort ได้ วิธีนี้ควรสร้างแผนซึ่งมีตัวดำเนินการคำนวณสเกลาร์เพื่อคำนวณมูลค่าสำหรับการสั่งซื้อและตัวดำเนินการเรียงลำดับ N อันดับแรกเพื่อรับแถวแรก แต่ทางด้านขวาคุณควรเห็น Nested Loop ที่ดีลองค้นหา CJ เป็นจำนวนมาก และประสิทธิภาพที่ดีกว่าการใช้ตารางจำนวนมากซึ่งไม่ตรงกับสิ่งใดใน DQ

Hash Match ไม่จำเป็นต้องน่ากลัว แต่ถ้าชุดของแถวที่คุณกลับมาจาก DQ นั้นเล็กกว่า CJ (อย่างที่ฉันคาดไว้) Hash Match จะสแกน CJ มากขึ้น กว่าที่มันต้องการ

หมายเหตุ: ฉันใช้ +1 แทน +0 เนื่องจากเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหามีแนวโน้มที่จะรับรู้ว่า +0 ไม่มีอะไรเปลี่ยนแปลง แน่นอนสิ่งเดียวกันอาจนำไปใช้กับ +1 ถ้าไม่ใช่ตอนนี้ในบางจุดในอนาคต


7

ฉันได้อ่านผลลัพธ์จากโพสต์นี้และเข้าใจแนวคิดของ Row Row ฯลฯ แล้วสิ่งที่ฉันอยากรู้คือฉันจะเปลี่ยนการสืบค้นอย่างไรเพื่อที่จะใช้แผนการที่ดีกว่า

การเพิ่มOPTION (QUERYTRACEON 4138)จะปิดผลของเป้าหมายแถวสำหรับแบบสอบถามนั้นเท่านั้นโดยไม่ต้องกำหนดมากเกินไปเกี่ยวกับแผนขั้นสุดท้ายและอาจเป็นวิธีที่ง่ายที่สุด / ตรงที่สุด

หากการเพิ่มคำใบ้นี้ให้ข้อผิดพลาดเกี่ยวกับการอนุญาต (จำเป็นสำหรับDBCC TRACEON) คุณสามารถนำไปใช้โดยใช้คำแนะนำแผน:

ใช้QUERYTRACEONในคู่มือแผนโดยspaghettidba

... หรือเพียงแค่ใช้ขั้นตอนการจัดเก็บ:

สิทธิ์ใดที่QUERYTRACEONต้องการ โดยKendra Little


3

SQL Server เวอร์ชันใหม่เสนอตัวเลือก (และดีกว่า arguably) ที่แตกต่างกันสำหรับการจัดการกับแบบสอบถามที่รับประสิทธิภาพที่ไม่ดีเมื่อเครื่องมือเพิ่มประสิทธิภาพสามารถใช้การปรับเป้าหมายแถวได้ SQL Server 2016 SP1 แนะนำสิ่งDISABLE_OPTIMIZER_ROWGOAL USE HINTที่มีผลเช่นเดียวกับการตั้งค่าสถานะการสืบค้นกลับ 4138 หากคุณไม่ได้อยู่ในรุ่นนั้นคุณสามารถลองใช้OPTIMIZE FORคำใบ้แบบสอบถามเพื่อรับแผนแบบสอบถามที่ออกแบบมาเพื่อคืนแถวทั้งหมดแทนที่จะเป็นเพียง 1 แบบสอบถามด้านล่าง จะส่งกลับผลลัพธ์เดียวกับคำถาม แต่จะไม่สร้างขึ้นโดยมีเป้าหมายในการรับเพียง 1 แถว

DECLARE @top INT = 1;

SELECT TOP (@top) dc.DOCUMENT_ID,
        dc.COPIES,
        dc.REQUESTOR,
        dc.D_ID,
        cj.FILE_NUMBER
FROM DOCUMENT_QUEUE dc
JOIN CORRESPONDENCE_JOURNAL cj
    ON dc.DOCUMENT_ID = cj.DOCUMENT_ID
WHERE dc.QUEUE_DATE <= GETDATE()
  AND dc.PRINT_LOCATION = 2
ORDER BY cj.FILE_NUMBER
OPTION (OPTIMIZE FOR (@top = 987654321));

2

เมื่อคุณทำTOP(1)ฉันขอแนะนำให้ORDER BYกำหนดขึ้นสำหรับการเริ่มต้น อย่างน้อยที่สุดสิ่งนี้จะช่วยให้มั่นใจว่าผลลัพธ์สามารถคาดการณ์ได้ตามหน้าที่ (มีประโยชน์เสมอสำหรับการทดสอบการถดถอย) ดูเหมือนว่าคุณจะต้องเพิ่มDC.D_IDและCJ.CORRESPONDENCE_IDสำหรับสิ่งนั้น

เมื่อมองไปที่แผนแบบสอบถามบางครั้งผมพบว่ามันให้คำแนะนำเพื่อให้ง่ายต่อการสอบถาม: อาจเลือกทั้งหมดแถว dc ที่เกี่ยวข้องในตาราง temp ล่วงหน้าเพื่อขจัดปัญหาเกี่ยวกับการประมาณ cardinality บนและQUEUE_DATE PRINT_LOCATIONสิ่งนี้ควรได้รับการนับอย่างรวดเร็ว จากนั้นคุณสามารถเพิ่มดัชนีลงในตาราง temp นี้หากจำเป็นโดยไม่ต้องเปลี่ยนตารางถาวร

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