สรุป
ปัญหาหลักคือ:
- การเลือกแผนของเครื่องมือเพิ่มประสิทธิภาพจะพิจารณาการกระจายค่าอย่างสม่ำเสมอ
- การขาดดัชนีที่เหมาะสมหมายถึง:
- การสแกนตารางเป็นเพียงตัวเลือกเดียว
- การเข้าร่วมเป็นการเข้าร่วมแบบลูปที่ไร้เดียงสามากกว่าการเข้าร่วมแบบลูปซ้อนดัชนี ในการเข้าร่วมที่ไร้เดียงสาภาคที่เข้าร่วมจะถูกประเมินที่การเข้าร่วมแทนที่จะถูกผลักลงด้านในของการเข้าร่วม
รายละเอียด
แผนสองแผนมีความคล้ายคลึงกันโดยพื้นฐานแม้ว่าประสิทธิภาพอาจแตกต่างกันมาก:
วางแผนด้วยคอลัมน์เพิ่มเติม
เลือกคอลัมน์ที่มีคอลัมน์พิเศษที่ไม่สมบูรณ์ในเวลาที่เหมาะสมก่อน:
คุณสมบัติที่น่าสนใจคือ:
- ด้านบนสุดที่โหนด 0 จำกัด แถวที่ส่งคืนไปที่ 100 และตั้งค่าเป้าหมายแถวสำหรับเครื่องมือเพิ่มประสิทธิภาพดังนั้นทุกอย่างที่อยู่ด้านล่างในแผนจะถูกเลือกให้ส่งคืน 100 แถวแรกอย่างรวดเร็ว
- สแกนที่โหนด 4 ค้นหาแถวจากตารางที่
Start_Time
ไม่ใช่ค่า null State
คือ 3 หรือ 4 และOperation_Type
เป็นหนึ่งในค่าที่แสดงรายการ ตารางจะถูกสแกนอย่างสมบูรณ์หนึ่งครั้งโดยแต่ละแถวจะถูกทดสอบกับเพรดิเคตที่กล่าวถึง เฉพาะแถวที่ผ่านการทดสอบทั้งหมดที่ไหลไปยังการเรียงลำดับ เครื่องมือเพิ่มประสิทธิภาพจะประมาณว่าจะมีคุณสมบัติ 38,283 แถว
- เรียงลำดับที่ 3 โหนดกินทุกแถวจากสแกนที่โหนด 4
Start_Time DESC
และทุกประเภทไว้ในคำสั่งของ นี่คือลำดับการนำเสนอสุดท้ายที่ร้องขอโดยแบบสอบถาม
- เครื่องมือเพิ่มประสิทธิภาพจะประมาณว่า 93 แถว (จริง ๆ แล้ว 93.2791) จะต้องอ่านจากการเรียงเพื่อให้แผนทั้งหมดกลับ 100 แถว (การบัญชีสำหรับผลกระทบที่คาดหวังจากการเข้าร่วม)
- Nested Loops เข้าร่วมที่โหนด 2 คาดว่าจะดำเนินการอินพุตด้านใน (สาขาที่ต่ำกว่า) 94 ครั้ง (อันที่จริง 94.2791) จำเป็นต้องใช้แถวพิเศษในการแลกเปลี่ยนหยุดคู่ขนานที่โหนด 1 ด้วยเหตุผลทางเทคนิค
- การสแกนที่โหนด 5 สแกนตารางในแต่ละการวนซ้ำอย่างสมบูรณ์ พบแถวที่
Start_Time
ไม่เป็นโมฆะและState
เป็น 3 หรือ 4 นี่คือประมาณในการผลิต 400,875 แถวในแต่ละรอบ การทำซ้ำมากกว่า 94.2791 จำนวนแถวทั้งหมดคือเกือบ 38 ล้าน
- การเข้าร่วมลูปซ้อนที่โหนด 2 ยังใช้เพรดิเคตการรวม ตรวจสอบว่า
Operation_Type
ตรงกับที่Start_Time
จากโหนด 4 น้อยกว่าStart_Time
จากโหนด 5 ว่าStart_Time
จากโหนด 5 น้อยกว่าFinish_Time
จากโหนด 4 และว่าทั้งสองId
ค่าไม่ตรงกัน
- Gather Streams (หยุดการแลกเปลี่ยนขนาน) ที่โหนด 1 ผสานกระแสข้อมูลที่สั่งจากแต่ละเธรดจนกว่าจะมีการสร้าง 100 แถว ลักษณะการเก็บรักษาตามคำสั่งของการรวมข้ามหลายสตรีมเป็นสิ่งที่ต้องการแถวพิเศษที่กล่าวถึงในขั้นตอนที่ 5
ความไร้ประสิทธิภาพยิ่งใหญ่เห็นได้ชัดในขั้นตอนที่ 6 และ 7 ด้านบน การสแกนตารางที่โหนด 5 อย่างสมบูรณ์สำหรับการทำซ้ำแต่ละครั้งนั้นมีความสมเหตุสมผลเพียงเล็กน้อยเท่านั้นหากมันเกิดขึ้น 94 เท่าเมื่อเครื่องมือเพิ่มประสิทธิภาพทำนาย ชุดเปรียบเทียบประมาณ 38 ล้านต่อแถวที่โหนด 2 ยังมีค่าใช้จ่ายสูง
สิ่งสำคัญอย่างยิ่งการประมาณเป้าหมายแถวแถว 93/94 นั้นมีแนวโน้มที่จะผิดเช่นกันเนื่องจากขึ้นอยู่กับการกระจายของค่า เครื่องมือเพิ่มประสิทธิภาพถือว่าการกระจายที่สม่ำเสมอในกรณีที่ไม่มีข้อมูลรายละเอียดเพิ่มเติม กล่าวง่ายๆว่าหมายความว่าหาก 1% ของแถวในตารางมีคุณสมบัติเหมาะสมเหตุผลของเครื่องมือเพิ่มประสิทธิภาพที่จะค้นหาแถวที่ตรงกัน 1 แถวจำเป็นต้องอ่าน 100 แถว
หากคุณเรียกใช้แบบสอบถามนี้จนเสร็จสมบูรณ์ (ซึ่งอาจใช้เวลานานมาก) คุณจะพบว่าต้องอ่านแถวมากกว่า 93/94 แถวจากแถวเรียงเพื่อที่จะสร้าง 100 แถวในที่สุด ในกรณีที่เลวร้ายที่สุดจะพบแถวที่ 100 โดยใช้แถวสุดท้ายจากการเรียงลำดับ สมมติว่าการประมาณค่าของเครื่องมือเพิ่มประสิทธิภาพที่โหนด 4 ถูกต้องนี่หมายถึงการรันการสแกนที่โหนด 5 38,284 ครั้งรวมทั้งหมดประมาณ 15 พันล้านแถว มันอาจจะมากขึ้นถ้าการประมาณสแกนถูกปิดเช่นกัน
แผนการดำเนินการนี้ยังมีคำเตือนดัชนีที่ขาดหายไป:
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 72.7096%.
WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([Operation_Type],[State],[Start_Time])
INCLUDE ([Id],[Parameters])
เครื่องมือเพิ่มประสิทธิภาพกำลังแจ้งเตือนคุณถึงข้อเท็จจริงที่ว่าการเพิ่มดัชนีลงในตารางจะช่วยเพิ่มประสิทธิภาพ
วางแผนโดยไม่มีคอลัมน์เพิ่มเติม
นี่เป็นแผนเดียวกับแผนก่อนหน้าโดยมีการเพิ่มดัชนีสปูลที่โหนด 6 และตัวกรองที่โหนด 5 ความแตกต่างที่สำคัญคือ:
- Index Spool ที่โหนด 6 เป็น Eager Spool มันกินผลของการสแกนที่ด้านล่างอย่างกระตือรือร้นและสร้างดัชนีชั่วคราวที่คีย์
Operation_Type
และStart_Time
มีId
คอลัมน์ที่ไม่ใช่คีย์
- การเข้าร่วมลูปซ้อนที่โหนด 2 ตอนนี้เป็นการเข้าร่วมดัชนี ไม่เข้าร่วมภาคจะมีการประเมินที่นี่แทนค่าปัจจุบันต่อย้ำ
Operation_Type
, Start_Time
, Finish_Time
และId
จากการสแกนที่โหนด 4 จะถูกส่งผ่านไปยังสาขาด้านข้างเป็นข้อมูลอ้างอิงด้านนอก
- การสแกนที่โหนด 7 จะดำเนินการเพียงครั้งเดียว
- Index Spool ที่โหนด 6 ค้นหาแถวจากดัชนีชั่วคราวที่
Operation_Type
ตรงกับค่าการอ้างอิงภายนอกปัจจุบันและStart_Time
อยู่ในช่วงที่กำหนดโดยการอ้างอิงภายนอกStart_Time
และFinish_Time
- ตัวกรองที่โหนด 5 การทดสอบ
Id
ค่าจากดัชนี Spool Id
สำหรับความไม่เท่าเทียมกันกับค่าอ้างอิงในปัจจุบันด้านนอกของ
การปรับปรุงที่สำคัญคือ:
- การสแกนด้านข้างจะดำเนินการเพียงครั้งเดียว
- ดัชนีชั่วคราวบน (
Operation_Type
, Start_Time
) พร้อมกับId
คอลัมน์ที่รวมไว้อนุญาตให้ดัชนีวนซ้อนกันเข้าร่วม ดัชนีใช้เพื่อค้นหาแถวที่ตรงกันในแต่ละการวนซ้ำมากกว่าการสแกนทั้งตารางในแต่ละครั้ง
เมื่อก่อนเครื่องมือเพิ่มประสิทธิภาพจะมีคำเตือนเกี่ยวกับดัชนีที่หายไป:
/*
The Query Processor estimates that implementing the following index
could improve the query cost by 24.1475%.
WARNING: This is only an estimate, and the Query Processor is making
this recommendation based solely upon analysis of this specific query.
It has not considered the resulting index size, or its workload-wide
impact, including its impact on INSERT, UPDATE, DELETE performance.
These factors should be taken into account before creating this index.
*/
CREATE NONCLUSTERED INDEX [<Name of Missing Index, sysname,>]
ON [dbo].[Batch_Tasks_Queue] ([State],[Start_Time])
INCLUDE ([Id],[Operation_Type])
GO
ข้อสรุป
แผนที่ไม่มีคอลัมน์เพิ่มเติมนั้นเร็วขึ้นเพราะเครื่องมือเพิ่มประสิทธิภาพเลือกสร้างดัชนีชั่วคราวสำหรับคุณ
แผนที่มีคอลัมน์เพิ่มเติมจะทำให้ดัชนีชั่วคราวมีราคาแพงกว่าในการสร้าง [Parameters
] คอลัมน์nvarchar(2000)
ซึ่งจะเพิ่มขึ้นถึง 4000 ไบต์ไปยังแถวของดัชนีแต่ละ ค่าใช้จ่ายเพิ่มเติมก็เพียงพอที่จะโน้มน้าวให้เครื่องมือเพิ่มประสิทธิภาพที่สร้างดัชนีชั่วคราวในแต่ละการดำเนินการจะไม่จ่ายสำหรับตัวเอง
เครื่องมือเพิ่มประสิทธิภาพเตือนทั้งสองกรณีว่าดัชนีถาวรจะเป็นทางออกที่ดีกว่า องค์ประกอบในอุดมคติของดัชนีขึ้นอยู่กับปริมาณงานที่มากขึ้นของคุณ สำหรับแบบสอบถามเฉพาะนี้ดัชนีที่แนะนำนั้นเป็นจุดเริ่มต้นที่สมเหตุสมผล แต่คุณควรเข้าใจถึงประโยชน์และต้นทุนที่เกี่ยวข้อง
คำแนะนำ
ดัชนีที่เป็นไปได้ที่หลากหลายจะเป็นประโยชน์สำหรับแบบสอบถามนี้ สิ่งที่สำคัญคือต้องมีดัชนีที่ไม่ได้จัดกลุ่มแบบบางอย่าง จากข้อมูลที่ให้ไว้ดัชนีที่สมเหตุสมผลในความเห็นของฉันจะเป็น:
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time);
ฉันจะถูกล่อลวงให้จัดระเบียบคิวรีให้ดีขึ้นเล็กน้อยและชะลอการค้นหา[Parameters]
คอลัมน์กว้างในดัชนีคลัสเตอร์จนกว่าจะพบแถว 100 แถวบนสุด (ใช้Id
เป็นคีย์):
SELECT TOP (100)
BTQ1.id,
BTQ2.id,
BTQ3.[Parameters],
BTQ4.[Parameters]
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
-- Look up the [Parameters] values
JOIN dbo.Batch_Tasks_Queue AS BTQ3
ON BTQ3.Id = BTQ1.Id
JOIN dbo.Batch_Tasks_Queue AS BTQ4
ON BTQ4.Id = BTQ2.Id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
-- These predicates are not strictly needed
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
ในกรณีที่[Parameters]
ไม่จำเป็นต้องใช้คอลัมน์การสืบค้นสามารถทำให้ง่ายขึ้นเพื่อ:
SELECT TOP (100)
BTQ1.id,
BTQ2.id
FROM dbo.Batch_Tasks_Queue AS BTQ1
JOIN dbo.Batch_Tasks_Queue AS BTQ2 WITH (FORCESEEK)
ON BTQ2.Operation_Type = BTQ1.Operation_Type
AND BTQ2.Start_Time > BTQ1.Start_Time
AND BTQ2.Start_Time < BTQ1.Finish_Time
AND BTQ2.id != BTQ1.id
WHERE
BTQ1.[State] IN (3, 4)
AND BTQ2.[State] IN (3, 4)
AND BTQ1.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ2.Operation_Type NOT IN (23, 24, 25, 26, 27, 28, 30)
AND BTQ1.Start_Time IS NOT NULL
AND BTQ2.Start_Time IS NOT NULL
ORDER BY
BTQ1.Start_Time DESC;
FORCESEEK
คำใบ้จะมีการช่วยให้เพิ่มประสิทธิภาพการเลือกลูปซ้อนกันจัดทำดัชนีวางแผน (มีสิ่งล่อใจที่ค่าใช้จ่ายที่ใช้สำหรับการเพิ่มประสิทธิภาพเพื่อเลือกกัญชาหรือ (หลาย ๆ อีกมากมาย) ผสานเข้าร่วมเป็นอย่างอื่นซึ่งมีแนวโน้มที่จะไม่ทำงานได้ดีกับชนิดของนี้ แบบสอบถามในทางปฏิบัติทั้งสองจบลงด้วยการเหลือขนาดใหญ่หลายรายการต่อถังในกรณีของแฮชและย้อนกลับจำนวนมากสำหรับการผสาน)
ทางเลือก
หากแบบสอบถาม (รวมถึงค่าเฉพาะ) มีความสำคัญอย่างยิ่งต่อประสิทธิภาพการอ่านฉันจะพิจารณาดัชนีที่กรองสองตัวแทน:
CREATE NONCLUSTERED INDEX i1
ON dbo.Batch_Tasks_Queue (Start_Time DESC)
INCLUDE (Operation_Type, [State], Finish_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
CREATE NONCLUSTERED INDEX i2
ON dbo.Batch_Tasks_Queue (Operation_Type, [State], Start_Time)
WHERE
Start_Time IS NOT NULL
AND [State] IN (3, 4)
AND Operation_Type <> 23
AND Operation_Type <> 24
AND Operation_Type <> 25
AND Operation_Type <> 26
AND Operation_Type <> 27
AND Operation_Type <> 28
AND Operation_Type <> 30;
สำหรับแบบสอบถามที่ไม่ต้องการ[Parameters]
คอลัมน์แผนโดยประมาณโดยใช้ดัชนีที่กรองคือ:
การสแกนดัชนีจะส่งคืนแถวที่มีคุณสมบัติทั้งหมดโดยอัตโนมัติโดยไม่ต้องประเมินภาคแสดงเพิ่มเติม สำหรับการวนซ้ำของการรวมลูปซ้อนของดัชนีแต่ละดัชนีการค้นหาจะทำการค้นหาสองครั้ง:
- แสวงหาการแข่งขันคำนำหน้าใน
Operation_Type
และState
= 3 แล้วที่กำลังมองหาช่วงของStart_Time
ค่ากริยาที่เหลืออยู่บนId
ความไม่เท่าเทียมกัน
- คำนำหน้าค้นหาจับคู่บน
Operation_Type
และState
= 4 จากนั้นค้นหาช่วงของStart_Time
ค่าคำอธิบายที่เหลือในId
ความไม่เท่าเทียม
ในกรณีที่[Parameters]
จำเป็นต้องใช้คอลัมน์แผนคิวรีจะเพิ่มการค้นหาเดี่ยวสูงสุด 100 ครั้งสำหรับแต่ละตาราง:
ในฐานะโน้ตสุดท้ายคุณควรพิจารณาใช้ชนิดจำนวนเต็มมาตรฐานในตัวแทนที่จะnumeric
ใช้