ใน SQL Server ฉันควรบังคับ LOOP JOIN ในกรณีต่อไปนี้หรือไม่


15

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

--Case 1: NO HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
JOIN SampleTable AS S ON S.ID = D.ID

--Case 2: LOOP JOIN HINT
SELECT S.*
INTO #Results
FROM #Driver AS D
INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID

SampleTable มี 1 ล้านแถวและ PK คือ ID
ตารางชั่วคราว #Driver มีเพียงหนึ่งคอลัมน์ ID ไม่มีดัชนีและแถว 50K

สิ่งที่ฉันค้นหาอย่างสม่ำเสมอมีดังต่อไปนี้:

กรณีที่ 1: ไม่มีคำแนะนำการ
สแกนดัชนี
แฮชของSampleTable เข้าร่วม
ระยะเวลาที่สูงขึ้น (เฉลี่ย 333 มิลลิวินาที)
CPU ที่สูงขึ้น (เฉลี่ย 331 มิลลิวินาที) การ
อ่านตรรกะที่ต่ำกว่า (4714)

กรณีที่ 2: LOOP เข้าร่วม
ดัชนีคำแนะนำค้นหา SampleTable
Loop เข้าร่วมช่วง
เวลาที่ต่ำกว่า (เฉลี่ย 204ms, 39% น้อยกว่า)
CPU ที่ลดลง (เฉลี่ย 206, 38% น้อยกว่า)
อ่านตรรกะที่สูงขึ้นมาก (160015, 34X เพิ่มเติม)

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

SampleTable มีอยู่ในหน้า 4714 ใช้เวลาประมาณ 36MB กรณีที่ 1 สแกนพวกเขาทั้งหมดซึ่งเป็นสาเหตุที่เราได้รับ 4714 อ่าน ยิ่งไปกว่านั้นจะต้องทำการแฮ็ค 1 ล้านแฮชซึ่งเป็น CPU ที่เข้มข้น ทั้งหมดนี้เป็นเพียงการคร่ำเครียดซึ่งดูเหมือนว่าจะทำให้เวลาในกรณีที่ 1

ตอนนี้ให้พิจารณากรณีที่ 2 มันไม่ได้ทำ hashing ใด ๆ แต่แทนที่จะทำ 50,000 ค้นหาแยกซึ่งเป็นสิ่งที่ผลักดันการอ่าน แต่ราคาอ่านแพงแค่ไหน? บางคนอาจบอกว่าถ้าอ่านแล้วมันอาจมีราคาแพง แต่โปรดจำไว้ว่า 1) เฉพาะการอ่านครั้งแรกของหน้าเว็บที่กำหนดอาจเป็นทางกายภาพและ 2) ดังนั้นแม้ว่ากรณีที่ 1 จะมีปัญหาเดียวกันหรือแย่กว่านั้นเนื่องจากรับประกันว่าจะตีทุกหน้า

ดังนั้นการบัญชีสำหรับข้อเท็จจริงที่ว่าทั้งสองกรณีต้องเข้าถึงแต่ละหน้าอย่างน้อยหนึ่งครั้งดูเหมือนคำถามที่เร็วกว่า 1 ล้านแฮชหรือประมาณ 155000 อ่านกับหน่วยความจำ? การทดสอบของฉันดูเหมือนจะพูดหลัง แต่ SQL Server เลือกอย่างสม่ำเสมอ

คำถาม

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

อัปเดต 2014-04-28

ฉันทำการทดสอบเพิ่มเติมและพบว่าผลลัพธ์ที่ฉันได้รับเหนือกว่า (บน VM w / 2 CPUs) ฉันไม่สามารถทำซ้ำในสภาพแวดล้อมอื่น ๆ (ฉันลองบนเครื่องทางกายภาพ 2 เครื่องที่มี 8 และ 12 CPU) เครื่องมือเพิ่มประสิทธิภาพทำได้ดีขึ้นมากในกรณีหลังจนถึงจุดที่ไม่มีปัญหาเด่นชัดดังกล่าว ฉันเดาว่าบทเรียนที่เรียนรู้ซึ่งดูเหมือนจะชัดเจนในการหวนกลับคือสภาพแวดล้อมสามารถส่งผลกระทบอย่างมีนัยสำคัญต่อวิธีที่เครื่องมือเพิ่มประสิทธิภาพทำงานได้ดี

แผนการดำเนินการ

กรณีแผนปฏิบัติการ 1 แผน 1 กรณีแผนปฏิบัติการ 2 ป้อนคำอธิบายรูปภาพที่นี่

รหัสเพื่อสร้างกรณีตัวอย่าง

------------------------------------------------------------
-- 1. Create SampleTable with 1,000,000 rows
------------------------------------------------------------    

CREATE TABLE SampleTable
    (  
       ID         INT NOT NULL PRIMARY KEY CLUSTERED
     , Number1    INT NOT NULL
     , Number2    INT NOT NULL
     , Number3    INT NOT NULL
     , Number4    INT NOT NULL
     , Number5    INT NOT NULL
    )

--Add 1 million rows
;WITH  
    Cte0 AS (SELECT 1 AS C UNION ALL SELECT 1), --2 rows  
    Cte1 AS (SELECT 1 AS C FROM Cte0 AS A, Cte0 AS B),--4 rows  
    Cte2 AS (SELECT 1 AS C FROM Cte1 AS A ,Cte1 AS B),--16 rows 
    Cte3 AS (SELECT 1 AS C FROM Cte2 AS A ,Cte2 AS B),--256 rows 
    Cte4 AS (SELECT 1 AS C FROM Cte3 AS A ,Cte3 AS B),--65536 rows 
    Cte5 AS (SELECT 1 AS C FROM Cte4 AS A ,Cte2 AS B),--1048576 rows 
    FinalCte AS (SELECT  ROW_NUMBER() OVER (ORDER BY C) AS Number FROM   Cte5)
INSERT INTO SampleTable
SELECT Number, Number, Number, Number, Number, Number
FROM  FinalCte
WHERE Number <= 1000000

------------------------------------------------------------
-- Create 2 SPs that join from #Driver to SampleTable.
------------------------------------------------------------    
GO
IF OBJECT_ID('JoinTest_NoHint') IS NOT NULL DROP PROCEDURE JoinTest_NoHint
GO
CREATE PROC JoinTest_NoHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    JOIN SampleTable AS S ON S.ID = D.ID
GO
IF OBJECT_ID('JoinTest_LoopHint') IS NOT NULL DROP PROCEDURE JoinTest_LoopHint
GO
CREATE PROC JoinTest_LoopHint
AS
    SELECT S.*
    INTO #Results
    FROM #Driver AS D
    INNER LOOP JOIN SampleTable AS S ON S.ID = D.ID
GO

------------------------------------------------------------
-- Create driver table with 50K rows
------------------------------------------------------------    
GO
IF OBJECT_ID('tempdb..#Driver') IS NOT NULL DROP TABLE #Driver
SELECT ID
INTO #Driver
FROM SampleTable
WHERE ID % 20 = 0

------------------------------------------------------------
-- Run each test and run Profiler
------------------------------------------------------------    

GO
/*Reg*/  EXEC JoinTest_NoHint
GO
/*Loop*/ EXEC JoinTest_LoopHint


------------------------------------------------------------
-- Results
------------------------------------------------------------    

/*

Duration CPU   Reads    TextData
315      313   4714     /*Reg*/  EXEC JoinTest_NoHint
309      296   4713     /*Reg*/  EXEC JoinTest_NoHint
327      329   4713     /*Reg*/  EXEC JoinTest_NoHint
398      406   4715     /*Reg*/  EXEC JoinTest_NoHint
316      312   4714     /*Reg*/  EXEC JoinTest_NoHint
217      219   160017   /*Loop*/ EXEC JoinTest_LoopHint
211      219   160014   /*Loop*/ EXEC JoinTest_LoopHint
217      219   160013   /*Loop*/ EXEC JoinTest_LoopHint
190      188   160013   /*Loop*/ EXEC JoinTest_LoopHint
187      187   160015   /*Loop*/ EXEC JoinTest_LoopHint

*/

คำตอบ:


13

SampleTable มีอยู่ในหน้า 4714 ใช้เวลาประมาณ 36MB กรณีที่ 1 สแกนพวกเขาทั้งหมดซึ่งเป็นสาเหตุที่เราได้รับ 4714 อ่าน ยิ่งไปกว่านั้นจะต้องทำการแฮ็ค 1 ล้านแฮชซึ่งเป็น CPU ที่เข้มข้น ทั้งหมดนี้เป็นเพียงการคร่ำเครียดซึ่งดูเหมือนว่าจะทำให้เวลาในกรณีที่ 1

มีค่าใช้จ่ายเริ่มต้นสำหรับการเข้าร่วมแฮช (การสร้างตารางแฮชซึ่งเป็นการดำเนินการบล็อก) แต่การเข้าร่วมแฮชท้ายที่สุดมีต้นทุนทางทฤษฎีต่ำสุดต่อแถวของการเข้าร่วมทางกายภาพทั้งสามประเภทที่สนับสนุนโดย SQL Server ทั้งใน เงื่อนไขของ IO และ CPU การรวมแฮชเข้ามาในตัวมันเองด้วยอินพุตบิวด์ที่ค่อนข้างเล็กและอินพุตโพรบขนาดใหญ่ ที่กล่าวว่าไม่มีประเภทการเข้าร่วมทางกายภาพที่ 'ดีกว่า' ในทุกสถานการณ์

ตอนนี้ให้พิจารณากรณีที่ 2 มันไม่ได้ทำ hashing ใด ๆ แต่แทนที่จะทำ 50,000 ค้นหาแยกซึ่งเป็นสิ่งที่ผลักดันการอ่าน แต่ค่าอ่านนั้นแพงแค่ไหน? อาจมีคนบอกว่าถ้าพวกเขาอ่านทางกายภาพอาจมีราคาแพง แต่โปรดจำไว้ว่า 1) เฉพาะการอ่านครั้งแรกของหน้าเว็บที่กำหนดอาจเป็นทางกายภาพและ 2) ดังนั้นแม้ว่ากรณีที่ 1 จะมีปัญหาเดียวกันหรือแย่กว่านั้นเนื่องจากรับประกันว่าจะตีทุกหน้า

การค้นหาแต่ละครั้งต้องใช้การนำทาง b-tree ไปที่รูทซึ่งมีราคาแพงเมื่อเทียบกับการแฮชโพรบเพียงครั้งเดียว นอกจากนี้รูปแบบ IO ทั่วไปสำหรับด้านในของการรวมการวนซ้ำแบบซ้อนจะเปรียบเทียบกับรูปแบบการเข้าถึงตามลำดับของอินพุตการสแกนด้านโพรบกับการแฮช ขึ้นอยู่กับระบบย่อยทางกายภาพ IO พื้นฐานการอ่านตามลำดับอาจเร็วกว่าการอ่านแบบสุ่ม นอกจากนี้กลไกการอ่านล่วงหน้าของ SQL Server ยังทำงานได้ดีขึ้นเมื่อใช้กับ IO ตามลำดับทำให้การอ่านมีขนาดใหญ่ขึ้น

ดังนั้นการบัญชีสำหรับข้อเท็จจริงที่ว่าทั้งสองกรณีต้องเข้าถึงแต่ละหน้าอย่างน้อยหนึ่งครั้งดูเหมือนคำถามที่เร็วกว่า 1 ล้านแฮชหรือประมาณ 155000 อ่านกับหน่วยความจำ? การทดสอบของฉันดูเหมือนจะพูดหลัง แต่ SQL Server เลือกอย่างสม่ำเสมอ

เครื่องมือเพิ่มประสิทธิภาพการสืบค้น SQL Server สร้างสมมติฐานหลายประการ หนึ่งคือการเข้าถึงหน้าแรกที่สร้างโดยการสืบค้นจะส่งผลให้เกิดฟิสิคัล IO ('ข้อสันนิษฐานแคชเย็น') โอกาสที่การอ่านในภายหลังจะมาจากหน้าที่อ่านแล้วในหน่วยความจำด้วยแบบสอบถามเดียวกันนั้นเป็นแบบจำลอง แต่นี่ไม่ได้เป็นการคาดเดาที่มีการศึกษามากกว่า

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

แผนที่สร้างขึ้นโดยใช้การสันนิษฐานแคชเย็นอาจไม่ได้ผลเช่นเดียวกับการสันนิษฐานแคชอุ่นแทน แต่ประสิทธิภาพที่แย่ที่สุดของกรณีมักจะดีกว่า

ฉันควรบังคับให้คำแนะนำ LOOP เข้าร่วมนี้ต่อเมื่อการทดสอบแสดงผลลัพธ์ประเภทนี้หรือฉันขาดการวิเคราะห์ ฉันลังเลที่จะเปรียบเทียบกับเครื่องมือเพิ่มประสิทธิภาพของ SQL Server แต่รู้สึกว่ามันเปลี่ยนเป็นการใช้แฮชเข้าร่วมเร็วกว่าที่ควรในกรณีเช่นนี้

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

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

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


ขอบคุณพอล การวิเคราะห์รายละเอียดที่ยอดเยี่ยม จากการทดสอบเพิ่มเติมที่ฉันทำฉันคิดว่าสิ่งที่เกิดขึ้นคือการคาดเดาที่เหมาะสมของตัวอย่างที่เฉพาะเจาะจงเมื่อขนาดของตาราง temp อยู่ระหว่าง 5K และ 100K จากความจริงที่ว่าความต้องการของเรารับประกันว่าโต๊ะอุณหภูมิจะ <50K ดูเหมือนว่าปลอดภัยสำหรับฉัน ฉันอยากรู้อยากเห็นคุณจะยังคงหลีกเลี่ยงการเข้าร่วมใด ๆ ที่รู้เรื่องนี้?
JohnnyM

1
@JohnnyM คำแนะนำมีอยู่ด้วยเหตุผล มันเป็นการดีที่จะใช้พวกเขาในที่ที่คุณมีเหตุผลที่ดีในการทำเช่นนั้น ที่กล่าวว่าผมไม่ค่อยใช้ร่วมFORCE ORDERคำแนะนำเพราะโดยนัย ในโอกาสที่แปลก ๆ ฉันใช้คำแนะนำการเข้าร่วมฉันมักจะเพิ่มOPTION (FORCE ORDER)ความคิดเห็นเพื่ออธิบายว่าทำไม
พอลไวท์ 9

0

50,000 แถวเชื่อมต่อกับตารางล้านแถวดูเหมือนว่าจะมีจำนวนมากสำหรับตารางใด ๆ ที่ไม่มีดัชนี

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

ยกตัวอย่างสิ่งที่บอกทำไมไม่ลองใส่ดัชนีใน #Driver? D.ID นั้นมีความพิเศษหรือไม่? ถ้าเป็นเช่นนั้นนั่นหมายถึงเทียบเท่ากับคำสั่ง EXISTS ซึ่งอย่างน้อยจะทำให้ SQL Server รู้ว่าคุณไม่ต้องการค้นหา S ซ้ำเพื่อหาค่าที่ซ้ำกันของ D:

SELECT S.*
INTO #Results
FROM SampleTable S
WHERE EXISTS (SELECT * #Driver D WHERE S.ID = D.ID);

ในระยะสั้นสำหรับรูปแบบนี้ฉันจะไม่ใช้คำใบ้ LOOP ฉันจะไม่ใช้รูปแบบนี้ ฉันจะทำอย่างใดอย่างหนึ่งต่อไปนี้ตามลำดับความสำคัญถ้า feasbile:

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