การค้นหาไม่ตอบสนองเมื่อเพิ่มสองคอลัมน์


9

เมื่อฉันเพิ่มสองคอลัมน์ลงในแบบสอบถามที่ฉันเลือกไม่ตอบสนอง nvarchar(2000)ประเภทของคอลัมน์ มันผิดปกติเล็กน้อย

  • เวอร์ชันเซิร์ฟเวอร์ SQL คือ 2014
  • มีดัชนีหลักเพียงหนึ่งดัชนีเท่านั้น
  • ระเบียนทั้งหมดมีเพียง 1,000 แถว

นี่คือแผนการดำเนินการก่อนหน้า ( XML showplan ):

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

แผนการดำเนินการหลังจาก ( XML showplan ):

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

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

select top(100)
  Batch_Tasks_Queue.id,
  btq.id,
  Batch_Tasks_Queue.[Parameters], -- this field
  btq.[Parameters]  -- and this field
from
        Batch_Tasks_Queue with(nolock)
    inner join  Batch_Tasks_Queue btq with(nolock)  on  Batch_Tasks_Queue.Start_Time < btq.Start_Time
                            and btq.Start_Time < Batch_Tasks_Queue.Finish_Time
                            and Batch_Tasks_Queue.id <> btq.id                            
                            and btq.Start_Time is not null
                            and btq.State in (3, 4)                          
where
    Batch_Tasks_Queue.Start_Time is not null      
    and Batch_Tasks_Queue.State in (3, 4)
    and Batch_Tasks_Queue.Operation_Type = btq.Operation_Type
    and Batch_Tasks_Queue.Operation_Type not in (23, 24, 25, 26, 27, 28, 30)

order by
    Batch_Tasks_Queue.Start_Time desc

จำนวนผลลัพธ์ทั้งหมดคือ 17 แถว ข้อมูลสกปรก (คำแนะนำ nolock) ไม่สำคัญ

นี่คือโครงสร้างของตาราง:

CREATE TABLE [dbo].[Batch_Tasks_Queue](
    [Id] [int] NOT NULL,
    [OBJ_VERSION] [numeric](8, 0) NOT NULL,
    [Operation_Type] [numeric](2, 0) NULL,
    [Request_Time] [datetime] NOT NULL,
    [Description] [varchar](1000) NULL,
    [State] [numeric](1, 0) NOT NULL,
    [Start_Time] [datetime] NULL,
    [Finish_Time] [datetime] NULL,
    [Parameters] [nvarchar](2000) NULL,
    [Response] [nvarchar](max) NULL,
    [Billing_UserId] [int] NOT NULL,
    [Planned_Start_Time] [datetime] NULL,
    [Input_FileId] [uniqueidentifier] NULL,
    [Output_FileId] [uniqueidentifier] NULL,
    [PRIORITY] [numeric](2, 0) NULL,
    [EXECUTE_SEQ] [numeric](2, 0) NULL,
    [View_Access] [numeric](1, 0) NULL,
    [Seeing] [numeric](1, 0) NULL,
 CONSTRAINT [PKBachTskQ] PRIMARY KEY CLUSTERED 
(
    [Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [Batch_Tasks_QueueData]
) ON [Batch_Tasks_QueueData] TEXTIMAGE_ON [Batch_Tasks_QueueData]
GO    
SET ANSI_PADDING OFF
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue]  WITH NOCHECK ADD  CONSTRAINT [FK0_BtchTskQ_BlngUsr] FOREIGN KEY([Billing_UserId])
REFERENCES [dbo].[BILLING_USER] ([ID])
GO
ALTER TABLE [dbo].[Batch_Tasks_Queue] CHECK CONSTRAINT [FK0_BtchTskQ_BlngUsr]
GO

การสนทนากับคำถามนี้ถูกย้ายไปที่ห้องสนทนานี้แล้ว
พอลไวท์ 9

คำตอบ:


15

สรุป

ปัญหาหลักคือ:

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

รายละเอียด

แผนสองแผนมีความคล้ายคลึงกันโดยพื้นฐานแม้ว่าประสิทธิภาพอาจแตกต่างกันมาก:

วางแผนด้วยคอลัมน์เพิ่มเติม

เลือกคอลัมน์ที่มีคอลัมน์พิเศษที่ไม่สมบูรณ์ในเวลาที่เหมาะสมก่อน:

แผนช้า

คุณสมบัติที่น่าสนใจคือ:

  1. ด้านบนสุดที่โหนด 0 จำกัด แถวที่ส่งคืนไปที่ 100 และตั้งค่าเป้าหมายแถวสำหรับเครื่องมือเพิ่มประสิทธิภาพดังนั้นทุกอย่างที่อยู่ด้านล่างในแผนจะถูกเลือกให้ส่งคืน 100 แถวแรกอย่างรวดเร็ว
  2. สแกนที่โหนด 4 ค้นหาแถวจากตารางที่Start_Timeไม่ใช่ค่า null Stateคือ 3 หรือ 4 และOperation_Typeเป็นหนึ่งในค่าที่แสดงรายการ ตารางจะถูกสแกนอย่างสมบูรณ์หนึ่งครั้งโดยแต่ละแถวจะถูกทดสอบกับเพรดิเคตที่กล่าวถึง เฉพาะแถวที่ผ่านการทดสอบทั้งหมดที่ไหลไปยังการเรียงลำดับ เครื่องมือเพิ่มประสิทธิภาพจะประมาณว่าจะมีคุณสมบัติ 38,283 แถว
  3. เรียงลำดับที่ 3 โหนดกินทุกแถวจากสแกนที่โหนด 4 Start_Time DESCและทุกประเภทไว้ในคำสั่งของ นี่คือลำดับการนำเสนอสุดท้ายที่ร้องขอโดยแบบสอบถาม
  4. เครื่องมือเพิ่มประสิทธิภาพจะประมาณว่า 93 แถว (จริง ๆ แล้ว 93.2791) จะต้องอ่านจากการเรียงเพื่อให้แผนทั้งหมดกลับ 100 แถว (การบัญชีสำหรับผลกระทบที่คาดหวังจากการเข้าร่วม)
  5. Nested Loops เข้าร่วมที่โหนด 2 คาดว่าจะดำเนินการอินพุตด้านใน (สาขาที่ต่ำกว่า) 94 ครั้ง (อันที่จริง 94.2791) จำเป็นต้องใช้แถวพิเศษในการแลกเปลี่ยนหยุดคู่ขนานที่โหนด 1 ด้วยเหตุผลทางเทคนิค
  6. การสแกนที่โหนด 5 สแกนตารางในแต่ละการวนซ้ำอย่างสมบูรณ์ พบแถวที่Start_Timeไม่เป็นโมฆะและStateเป็น 3 หรือ 4 นี่คือประมาณในการผลิต 400,875 แถวในแต่ละรอบ การทำซ้ำมากกว่า 94.2791 จำนวนแถวทั้งหมดคือเกือบ 38 ล้าน
  7. การเข้าร่วมลูปซ้อนที่โหนด 2 ยังใช้เพรดิเคตการรวม ตรวจสอบว่าOperation_Typeตรงกับที่Start_Timeจากโหนด 4 น้อยกว่าStart_Timeจากโหนด 5 ว่าStart_Timeจากโหนด 5 น้อยกว่าFinish_Timeจากโหนด 4 และว่าทั้งสองIdค่าไม่ตรงกัน
  8. 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 ความแตกต่างที่สำคัญคือ:

  1. Index Spool ที่โหนด 6 เป็น Eager Spool มันกินผลของการสแกนที่ด้านล่างอย่างกระตือรือร้นและสร้างดัชนีชั่วคราวที่คีย์Operation_TypeและStart_TimeมีIdคอลัมน์ที่ไม่ใช่คีย์
  2. การเข้าร่วมลูปซ้อนที่โหนด 2 ตอนนี้เป็นการเข้าร่วมดัชนี ไม่เข้าร่วมภาคจะมีการประเมินที่นี่แทนค่าปัจจุบันต่อย้ำOperation_Type, Start_Time, Finish_TimeและIdจากการสแกนที่โหนด 4 จะถูกส่งผ่านไปยังสาขาด้านข้างเป็นข้อมูลอ้างอิงด้านนอก
  3. การสแกนที่โหนด 7 จะดำเนินการเพียงครั้งเดียว
  4. Index Spool ที่โหนด 6 ค้นหาแถวจากดัชนีชั่วคราวที่Operation_Typeตรงกับค่าการอ้างอิงภายนอกปัจจุบันและStart_Timeอยู่ในช่วงที่กำหนดโดยการอ้างอิงภายนอกStart_TimeและFinish_Time
  5. ตัวกรองที่โหนด 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]คอลัมน์แผนโดยประมาณโดยใช้ดัชนีที่กรองคือ:

แผนดัชนีกรองง่าย

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

  1. แสวงหาการแข่งขันคำนำหน้าในOperation_TypeและState= 3 แล้วที่กำลังมองหาช่วงของStart_Timeค่ากริยาที่เหลืออยู่บนIdความไม่เท่าเทียมกัน
  2. คำนำหน้าค้นหาจับคู่บนOperation_TypeและState= 4 จากนั้นค้นหาช่วงของStart_Timeค่าคำอธิบายที่เหลือในIdความไม่เท่าเทียม

ในกรณีที่[Parameters]จำเป็นต้องใช้คอลัมน์แผนคิวรีจะเพิ่มการค้นหาเดี่ยวสูงสุด 100 ครั้งสำหรับแต่ละตาราง:

แผนดัชนีที่กรองด้วยคอลัมน์เพิ่มเติม

ในฐานะโน้ตสุดท้ายคุณควรพิจารณาใช้ชนิดจำนวนเต็มมาตรฐานในตัวแทนที่จะnumericใช้


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