ถ้ามีอยู่ใช้เวลานานกว่าคำสั่ง select ฝังตัว


35

เมื่อฉันเรียกใช้รหัสต่อไปนี้มันใช้เวลา 22.5 นาทีและ 106 ล้านอ่าน อย่างไรก็ตามถ้าฉันเรียกใช้เพียงคำสั่งเลือกภายในด้วยตัวเองมันใช้เวลาเพียง 15 วินาทีและอ่านได้ 264k ในฐานะที่เป็นข้อความด้านแบบสอบถามแบบใช้เลือกข้อมูลจะไม่ส่งกลับระเบียน

ความคิดใดที่ว่าทำไมIF EXISTSมันถึงทำให้มันใช้งานได้นานขึ้นและอ่านได้มากขึ้น? ฉันเปลี่ยนคำสั่ง select เป็นสิ่งที่ต้องทำSELECT TOP 1 [dlc].[id]และฉันก็ฆ่ามันหลังจาก 2 นาที

ในฐานะที่เป็นแก้ไขชั่วคราวฉันมีการเปลี่ยนแปลงจะทำนับ (*) @cntและกำหนดค่าที่ให้กับตัวแปร จากนั้นจะมีIF 0 <> @cntคำสั่ง แต่ฉันคิดว่าEXISTSจะดีกว่าเพราะถ้ามีการบันทึกกลับมาในคำสั่ง select มันจะหยุดทำการสแกน / ค้นหาเมื่อพบอย่างน้อยหนึ่งระเบียนในขณะที่count(*)จะทำการสืบค้นแบบเต็ม ฉันพลาดอะไรไป

IF EXISTS
   (SELECT [dlc].[ID]
   FROM TableDLC [dlc]
   JOIN TableD [d]
   ON [d].[ID] = [dlc].[ID]
   JOIN TableC [c]
   ON [c].[ID] = [d].[ID2]
   WHERE [c].[Name] <> [dlc].[Name])
BEGIN
   <do something>
END

4
เพื่อหลีกเลี่ยงปัญหาเป้าหมายแถวความคิดอื่น (ยังไม่ทดลองใจคุณ!) อาจจะมีการพยายามที่ผกผัน IF NOT EXISTS (...) BEGIN END ELSE BEGIN <do something> END-
Aaron Bertrand

คำตอบ:


32

ความคิดใดที่ว่าทำไมIF EXISTSมันถึงทำให้มันใช้งานได้นานขึ้นและอ่านได้มากขึ้น? ฉันเปลี่ยนคำสั่ง select เป็นสิ่งที่ต้องทำSELECT TOP 1 [dlc].[id]และฉันก็ฆ่ามันหลังจาก 2 นาที

ตามที่ฉันอธิบายในคำตอบของคำถามที่เกี่ยวข้องนี้:

TOP (และทำไม) ส่งผลกระทบต่อแผนการดำเนินการอย่างไร

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

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

เป็นการแก้ไขชั่วคราวฉันได้ทำการเปลี่ยนแปลงเพื่อทำการนับ (*) และกำหนดค่าให้กับตัวแปร

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

หากคุณใช้ SQL Server 2008 R2 หรือใหม่กว่าคุณสามารถใช้เอกสารที่ได้รับการสนับสนุนและสนับสนุนการตั้งค่าสถานะการสืบค้นกลับ 4138เพื่อรับแผนการดำเนินการโดยไม่มีเป้าหมายแถว นอกจากนี้ยังสามารถระบุการตั้งค่าสถานะนี้ได้โดยใช้คำใบ้ที่ได้รับ การสนับสนุนOPTION (QUERYTRACEON 4138)แต่โปรดทราบว่ามันต้องได้รับอนุญาตดูแลระบบรันไทม์ยกเว้นว่าใช้กับคำแนะนำแผน

น่าเสียดาย

ไม่มีข้อใดถูกใช้งานได้กับIF EXISTSข้อความสั่งแบบมีเงื่อนไข ใช้กับ DML ปกติเท่านั้น มันจะทำงานกับSELECT TOP (1)สูตรทางเลือกที่คุณลอง นั่นอาจจะดีกว่าการใช้COUNT(*)งานซึ่งจะต้องนับจำนวนแถวที่ผ่านการรับรองตามที่ได้กล่าวไปแล้วทั้งหมด

ที่กล่าวว่ามีหลายวิธีที่จะแสดงความต้องการนี้ที่จะช่วยให้คุณหลีกเลี่ยงหรือควบคุมเป้าหมายแถวในขณะที่ยกเลิกการค้นหาก่อน หนึ่งตัวอย่างสุดท้าย:

DECLARE @Exists bit;

SELECT @Exists =
    CASE
        WHEN EXISTS
        (
            SELECT [dlc].[ID]
            FROM TableDLC [dlc]
            JOIN TableD [d]
            ON [d].[ID] = [dlc].[ID]
            JOIN TableC [c]
            ON [c].[ID] = [d].[ID2]
            WHERE [c].[Name] <> [dlc].[Name]
        )
        THEN CONVERT(bit, 1)
        ELSE CONVERT(bit, 0)
    END
OPTION (QUERYTRACEON 4138);

IF @Exists = 1
BEGIN
    ...
END;

ตัวอย่าง alt ที่คุณระบุนั้นใช้เวลา 3.75 นาทีและอ่าน 46m ดังนั้นในขณะที่เร็วกว่าแบบสอบถามเดิมของฉันฉันคิดว่าในกรณีนี้ฉันจะยึดด้วย @cnt = count (*) และประเมินตัวแปรหลังจากนั้น โดยเฉพาะอย่างยิ่งตั้งแต่ 99% ของเวลานี้จะไม่มีอะไรในนั้น ดูเหมือนว่าขึ้นอยู่กับคำตอบของคุณและ Rob ว่ามีอยู่ก็ต่อเมื่อคุณคาดหวังผลลัพธ์บางประเภทเท่านั้นและผลลัพธ์นั้นจะกระจายอย่างเท่าเทียมกันภายในข้อมูลของคุณ
Chris Woods

3
@ChrisWoods: คุณพูดว่า "โดยเฉพาะอย่างยิ่งตั้งแต่ 99% ของเวลานี้จะไม่มีอะไรในนั้น" สิ่งนี้รับประกันได้เลยว่าเป้าหมายของแถวหนึ่งเป็นความคิดที่ไม่ดีเนื่องจากคุณคาดหวังว่าจะไม่มีแถวและต้องสแกนทุกอย่างเพื่อหาว่าไม่มี หากคุณไม่สามารถเพิ่มดัชนีที่ฉลาดได้ให้ใช้ COUNT (*)
Ross Presser

25

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

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

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