เหตุใดการเลือกคอลัมน์ผลลัพธ์ทั้งหมดของแบบสอบถามนี้จึงเร็วกว่าการเลือกคอลัมน์เดียวที่ฉันสนใจ


13

ฉันมีแบบสอบถามที่ใช้select *ไม่เพียง แต่อ่านน้อยลง แต่ยังใช้เวลา CPU น้อยกว่าการใช้select c.Fooอย่างมาก

นี่คือแบบสอบถาม:

select top 1000 c.ID
from ATable a
    join BTable b on b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
    join CTable c on c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
where (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff)
    and b.IsVoided = 0
    and c.ComplianceStatus in (3, 5)
    and c.ShipmentStatus in (1, 5, 6)
order by a.LastAnalyzedDate

สิ่งนี้เสร็จสิ้นด้วยการอ่านแบบลอจิคัล 2,473,658 ตัวส่วนใหญ่ในตาราง B มันใช้ CPU 26,562 ตัวและมีระยะเวลา 7,965

นี่คือแผนแบบสอบถามที่สร้างขึ้น:

วางแผนจากการเลือกค่าของคอลัมน์เดี่ยว ใน PasteThePlan: https://www.brentozar.com/pastetheplan/?id=BJAp2mQIQ

เมื่อฉันเปลี่ยนc.IDไป*แบบสอบถามจบด้วย 107,049 ตรรกะอ่านธรรมกระจายอย่างสม่ำเสมอระหว่างทั้งสามตาราง มันใช้ซีพียู 4,266 ตัวและมีช่วงเวลา 1,147

นี่คือแผนแบบสอบถามที่สร้างขึ้น:

วางแผนจากการเลือกค่าทั้งหมด ใน PasteThePlan: https://www.brentozar.com/pastetheplan/?id=SyZYn7QUQ

ฉันพยายามใช้คำแนะนำการค้นหาที่แนะนำโดย Joe Obbish ด้วยผลลัพธ์เหล่านี้
select c.ID: https://www.brentozar.com/pastetheplan/?id=SJfBdOELm
select c.IDคำแนะนำ: https://www.brentozar.com/pastetheplan/ ? id = B1W ___ N87
select *โดยไม่มีคำใบ้: https://www.brentozar.com/pastetheplan/?id=HJ6qddEIm
select *พร้อมคำใบ้: https://www.brentozar.com/pastetheplan/?id=rJhhudNIQ

การใช้OPTION(LOOP JOIN)คำใบ้ด้วยselect c.IDนั้นลดจำนวนการอ่านลงอย่างมากเมื่อเทียบกับรุ่นที่ไม่มีคำใบ้ แต่ก็ยังคงทำประมาณ 4x จำนวนการอ่านselect *คำค้นหาโดยไม่มีคำใบ้ใด ๆ การเพิ่มOPTION(RECOMPILE, HASH JOIN)ในselect *แบบสอบถามทำให้ประสิทธิภาพแย่ลงกว่าสิ่งอื่นใดที่ฉันได้ลองไปแล้ว

หลังจากการปรับปรุงสถิติในตารางและดัชนีของตนโดยใช้WITH FULLSCANการselect c.IDแบบสอบถามกำลังทำงานได้เร็วขึ้นมาก:
select c.IDก่อนที่จะปรับปรุง: https://www.brentozar.com/pastetheplan/?id=SkiYoOEUm
select *ก่อนการปรับปรุง: https://www.brentozar.com/ pastetheplan /? id = ryrvodEUX
select c.IDหลังจากอัปเดต: https://www.brentozar.com/pastetheplan/?id=B1MRoO487
select *หลังจากอัปเดต: https://www.brentozar.com/pastetheplan/?id=Hk7si_V8m

select *ยังคงมีประสิทธิภาพสูงกว่าselect c.IDในแง่ของระยะเวลาทั้งหมดและการอ่านทั้งหมด ( select *มีประมาณครึ่งหนึ่งที่อ่าน) แต่มันใช้ CPU มากขึ้น โดยรวมแล้วพวกเขาจะเข้าใกล้กว่าก่อนการอัพเดทมาก แต่แผนยังคงแตกต่างกัน

พฤติกรรมแบบเดียวกันนั้นเห็นได้ในปี 2016 ที่ทำงานในโหมดความเข้ากันได้ในปี 2014 และในปี 2014 อะไรที่สามารถอธิบายความแตกต่างระหว่างสองแผน เป็นไปได้หรือไม่ที่ดัชนี "ถูกต้อง" ยังไม่ถูกสร้างขึ้น? สถิติที่ล้าสมัยเล็กน้อยอาจทำให้เกิดสิ่งนี้หรือไม่

ฉันพยายามย้ายภาคแสดงขึ้นเป็นONส่วนหนึ่งของการเข้าร่วมหลายวิธี แต่แผนแบบสอบถามจะเหมือนกันทุกครั้ง

หลังจากสร้างดัชนีใหม่

ฉันสร้างดัชนีทั้งหมดอีกครั้งในสามตารางที่เกี่ยวข้องในแบบสอบถาม c.IDยังคงอ่านมากที่สุด (มากกว่าสองเท่า*) แต่การใช้งาน CPU อยู่ประมาณครึ่งหนึ่งของ*รุ่น c.IDรุ่นยังหกเข้าไป tempdb ในการเรียงลำดับของATable:
c.ID: https://www.brentozar.com/pastetheplan/?id=HyHIeDO87
* : https://www.brentozar.com/pastetheplan/?id=rJ4deDOIQ

ฉันพยายามบังคับให้มันทำงานโดยไม่ขนานกันและนั่นทำให้ฉันได้รับแบบสอบถามที่มีประสิทธิภาพที่สุด: https://www.brentozar.com/pastetheplan/?id=SJn9-vuLX

ฉันสังเกตเห็นว่าจำนวนผู้ดำเนินการหลังจากดัชนีขนาดใหญ่ค้นหาที่ทำการสั่งดำเนินการเพียง 1,000 ครั้งในเวอร์ชันเธรดเดียว แต่มีความสำคัญมากกว่าในเวอร์ชันแบบขนานระหว่าง 2,622 ถึง 4,315 การดำเนินการของตัวดำเนินการต่างๆ

คำตอบ:


4

เป็นความจริงที่การเลือกคอลัมน์เพิ่มเติมหมายความว่า SQL Server อาจต้องทำงานหนักขึ้นเพื่อให้ได้ผลลัพธ์ที่ต้องการของแบบสอบถาม หากเครื่องมือเพิ่มประสิทธิภาพคิวรีสามารถสร้างแผนคิวรีที่สมบูรณ์แบบสำหรับทั้งคิวรีได้มันก็น่าจะสมเหตุสมผลSELECT *แบบสอบถามเพื่อให้ทำงานได้นานกว่าแบบสอบถามที่เลือกคอลัมน์ทั้งหมดจากตารางทั้งหมด คุณสังเกตเห็นตรงกันข้ามกับคำค้นหาคู่ของคุณ คุณต้องระมัดระวังเมื่อเปรียบเทียบค่าใช้จ่าย แต่ข้อความค้นหาที่ช้ามีค่าใช้จ่ายโดยรวมประมาณ 1,090.08 หน่วยเครื่องมือเพิ่มประสิทธิภาพและแบบสอบถามแบบรวดเร็วมีค่าใช้จ่ายโดยประมาณทั้งหมดเป็นหน่วยเครื่องมือเพิ่มประสิทธิภาพ 6823.11 ในกรณีนี้อาจกล่าวได้ว่าเครื่องมือเพิ่มประสิทธิภาพทำงานได้ไม่ดีโดยประมาณค่าใช้จ่ายแบบสอบถามทั้งหมด มันเลือกแผนอื่นสำหรับคิวรี SELECT * ของคุณและคาดว่าแผนนั้นจะมีราคาแพงกว่า แต่นั่นไม่ใช่กรณีที่นี่ ความไม่ตรงกันประเภทนั้นสามารถเกิดขึ้นได้จากหลายสาเหตุและหนึ่งในสาเหตุที่พบบ่อยที่สุดคือปัญหาการประเมินภาวะหัวใจขาดเลือด ค่าใช้จ่ายของผู้ประกอบการส่วนใหญ่จะถูกกำหนดโดยการประเมิน cardinality หากการประเมินความสำคัญเชิงหัวใจที่จุดสำคัญในแผนไม่ถูกต้องค่าใช้จ่ายทั้งหมดของแผนอาจไม่สะท้อนความเป็นจริง นี่คือการทำให้เข้าใจผิดโดยรวม แต่ฉันหวังว่ามันจะมีประโยชน์สำหรับการทำความเข้าใจว่าเกิดอะไรขึ้นที่นี่

เริ่มต้นด้วยการอภิปรายว่าทำไมSELECT *แบบสอบถามอาจมีราคาแพงกว่าการเลือกคอลัมน์เดียว SELECT *แบบสอบถามอาจหันดัชนีครอบคลุมบางอย่างในดัชนี noncovering ซึ่งอาจหมายความว่าความต้องการที่เพิ่มประสิทธิภาพในการทำงานนอกจากนี้จะได้รับทั้งหมดของคอลัมน์ที่จำเป็นหรือมันอาจจะต้องอ่านจากดัชนีที่มีขนาดใหญ่SELECT *อาจส่งผลให้ชุดผลลัพธ์ระดับกลางที่ใหญ่กว่าซึ่งจำเป็นต้องประมวลผลระหว่างการดำเนินการค้นหา คุณสามารถดูการทำงานนี้ได้โดยดูที่ขนาดแถวโดยประมาณในทั้งสองข้อความค้นหา ในแบบสอบถามอย่างรวดเร็วขนาดแถวของคุณมีตั้งแต่ 664 ไบต์ถึง 3019 ไบต์ ในคิวรีแบบช้าขนาดของแถวมีตั้งแต่ 19 ถึง 36 ไบต์ ตัวดำเนินการบล็อกเช่นการเรียงลำดับหรือการสร้างแฮชจะมีค่าใช้จ่ายสูงกว่าสำหรับข้อมูลที่มีขนาดแถวที่ใหญ่กว่าเนื่องจาก SQL Server รู้ดีกว่าการเรียงลำดับข้อมูลจำนวนมากหรือเปลี่ยนเป็นตารางแฮช

มองไปที่การค้นหาได้อย่างรวดเร็วและเพิ่มประสิทธิภาพการประมาณการที่ว่ามันต้องทำ 2.4 Database1.Schema1.Object5.Index3ล้านดัชนีพยายามใน นั่นคือที่มาของต้นทุนส่วนใหญ่มาจาก แต่แผนจริงเผยให้เห็นว่ามีการค้นหาดัชนีเพียง 1332 ตัวที่ดำเนินการกับตัวดำเนินการนั้น หากคุณเปรียบเทียบค่าจริงกับแถวที่ประมาณไว้สำหรับชิ้นส่วนภายนอกของการวนซ้ำนั้นคุณจะเห็นความแตกต่างมาก เครื่องมือเพิ่มประสิทธิภาพคิดว่าจะต้องมีการค้นหาดัชนีจำนวนมากเพื่อค้นหา 1,000 แถวแรกที่จำเป็นสำหรับผลลัพธ์ของแบบสอบถาม นั่นเป็นสาเหตุที่คิวรีมีแผนต้นทุนที่ค่อนข้างสูง แต่เสร็จเร็วมาก: ผู้ดำเนินการที่คาดการณ์ว่าจะมีราคาแพงที่สุดนั้นน้อยกว่า 0.1% ของงานที่คาดหวัง

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

คุณอาจพบว่าเป็นประโยชน์ในการเพิ่มคำแนะนำแบบสอบถามทั้งสองรุ่นของแบบสอบถามเพื่อบังคับแผนแบบสอบถามที่เกี่ยวข้องกับรุ่นอื่น คำแนะนำการสืบค้นอาจเป็นเครื่องมือที่ดีในการหาสาเหตุที่เครื่องมือเพิ่มประสิทธิภาพสร้างทางเลือกบางอย่าง หากคุณเพิ่มลงOPTION (RECOMPILE, HASH JOIN)ในSELECT *แบบสอบถามที่ฉันคาดว่าคุณจะเห็นแผนแบบสอบถามที่คล้ายกันกับแบบสอบถามเข้าร่วมแฮช ฉันยังคาดว่าค่าใช้จ่ายแบบสอบถามจะสูงขึ้นมากสำหรับแผนการเข้าร่วมแฮชเนื่องจากขนาดแถวของคุณใหญ่กว่ามาก ดังนั้นอาจเป็นสาเหตุที่คิวรีการเข้าร่วมแฮชไม่ได้ถูกเลือกสำหรับการSELECT *สืบค้น หากคุณเพิ่มลงOPTION (LOOP JOIN)ในคิวรีที่เลือกเพียงหนึ่งคอลัมน์ฉันคาดว่าคุณจะเห็นแผนคิวรีคล้ายกับคอลัมน์สำหรับSELECT *สอบถาม ในกรณีนี้การลดขนาดแถวจะไม่ส่งผลกระทบต่อต้นทุนการค้นหาโดยรวมมากนัก คุณอาจข้ามการค้นหาคีย์ แต่นั่นเป็นค่าใช้จ่ายเล็กน้อยโดยประมาณ

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


ขอบคุณมากสำหรับคำตอบที่กว้างขวางและให้ข้อมูลของคุณ ฉันพยายามเพิ่มคำแนะนำที่คุณแนะนำ มันทำให้การselect c.IDสืบค้นเร็วขึ้นมาก แต่ก็ยังคงทำงานพิเศษที่การselect *สืบค้นโดยไม่ต้องใส่คำใบ้
L. Miller

2

สถิติเก่า ๆ อาจทำให้เครื่องมือเพิ่มประสิทธิภาพเลือกวิธีการที่ดีในการค้นหาข้อมูล คุณเคยลองทำUPDATE STATISTICS ... WITH FULLSCANหรือทำเต็มREBUILDดัชนีหรือไม่? ลองและดูว่ามันจะช่วย

UPDATE

ตามการอัพเดทจาก OP:

หลังจากการปรับปรุงสถิติในตารางและดัชนีของตนโดยใช้WITH FULLSCANการselect c.IDแบบสอบถามกำลังทำงานได้เร็วขึ้นมาก

ดังนั้นตอนนี้ถ้าการกระทำเพียงอย่างเดียวคือUPDATE STATISTICSให้ลองทำดัชนีREBUILD(ไม่ใช่REORGANIZE) ดังที่ฉันได้เห็นความช่วยเหลือเกี่ยวกับการประมาณแถวนับว่าทั้งสองUPDATE STATISTICSและดัชนีREORGANIZEไม่ได้


ฉันสามารถรับดัชนีทั้งหมดในสามตารางที่เกี่ยวข้องเพื่อสร้างใหม่ในช่วงสุดสัปดาห์และได้อัปเดตโพสต์ของฉันเพื่อสะท้อนผลลัพธ์เหล่านั้น
L. Miller

-1
  1. คุณช่วยรวมสคริปต์ดัชนีไว้ด้วยได้ไหม?
  2. คุณกำจัดปัญหาที่อาจเกิดขึ้นกับ "การดมกลิ่นพารามิเตอร์" หรือไม่? https://www.mssqltips.com/sqlservertip/3257/different-approaches-to-correct-sql-server-parameter-sniffing/
  3. ฉันได้พบเทคนิคนี้จะเป็นประโยชน์ในบางกรณี:
    a) เขียนแต่ละตารางเป็นแบบสอบถามย่อยตามกฎเหล่านี้:
    b) เลือก - ใส่คอลัมน์เข้าร่วมก่อน
    c) กำหนด - ย้ายไปที่แบบสอบถามย่อยตามลำดับ
    d) ORDER BY
    เคียวรีย่อยที่เกี่ยวข้องเรียงลำดับในคอลัมน์แรกเข้าe) เพิ่มเคียวรี wrapper สำหรับการเรียงลำดับสุดท้ายของคุณและเลือก

แนวคิดคือการเรียงลำดับคอลัมน์เข้าร่วมล่วงหน้าภายในแต่ละการเลือกย่อยโดยใส่คอลัมน์เข้าร่วมก่อนในรายการที่เลือกแต่ละรายการ

นี่คือสิ่งที่ฉันหมายถึง ....

SELECT ... wrapper query
FROM
(
    SELECT ...
    FROM
        (SELECT ClientID, ShipKey, NextAnalysisDate
         FROM ATABLE
         WHERE (a.NextAnalysisDate is null or a.NextAnalysisDate < @dateCutOff) -- Predicates
         ORDER BY OrderKey, ClientID, LastAnalyzedDate  ---- Pre-sort the join columns
        ) as a
        JOIN 
        (SELECT OrderKey, ClientID, OrderID, IsVoided
         FROM BTABLE
         WHERE IsVoided = 0             ---- Include all predicates
         ORDER BY OrderKey, OrderID, IsVoided       ---- Pre-sort the join columns
        ) as b ON b.OrderKey = a.OrderKey and b.ClientId = a.ClientId
        JOIN
        (SELECT OrderID, ShipKey, ComplianceStatus, ShipmentStatus, ID
         FROM CTABLE
         WHERE ComplianceStatus in (3, 5)       ---- Include all predicates
             AND ShipmentStatus in (1, 5, 6)        ---- Include all predicates
         ORDER BY OrderID, ShipKey          ---- Pre-sort the join columns
        ) as c ON c.OrderId = b.OrderId and c.ShipKey = a.ShipKey
) as d
ORDER BY d.LastAnalyzedDate

1
1. ฉันจะพยายามเพิ่มสคริปต์ DDL ดัชนีลงในโพสต์ดั้งเดิมซึ่งอาจใช้เวลาสักครู่ในการ "ขัด" พวกเขา 2. ฉันทดสอบความเป็นไปได้นี้โดยการล้างแคชแผนก่อนเรียกใช้และโดยการแทนที่พารามิเตอร์ bind ด้วยค่าจริง 3. ฉันพยายามทำสิ่งนี้ แต่ORDER BYไม่ถูกต้องในข้อความค้นหาย่อยที่ไม่มี TOP, FORXML และอื่น ๆ ฉันลองใช้โดยไม่มีส่วนORDER BYคำสั่ง แต่เป็นแผนเดียวกัน
L. Miller
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.