การรวม ORDER BY บนเคียวรีที่ส่งคืนแถวไม่มีผลกระทบต่อประสิทธิภาพอย่างมาก


15

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

SELECT * 
FROM tinytable t                          /* one narrow row */
JOIN smalltable s on t.id=s.tinyId        /* one narrow row */
JOIN bigtable b on b.smallGuidId=s.GuidId /* a million narrow rows */
WHERE t.foreignId=3                       /* doesn't match */
ORDER BY b.CreatedUtc          /* try with and without this ORDER BY */

ฉันเข้าใจว่าฉันสามารถมีดัชนีใน bigtable.smallGuidId ได้ แต่ฉันเชื่อว่ามันจะทำให้แย่ลงในกรณีนี้

นี่คือสคริปต์เพื่อสร้าง / เติมตารางสำหรับการทดสอบ อยากรู้อยากเห็นดูเหมือนว่าเรื่องเล็ก ๆ ที่มีเขตข้อมูล nvarchar (สูงสุด) มันก็ดูเหมือนว่าจะสำคัญว่าฉันเข้าร่วมในตารางใหญ่ด้วย guid (ซึ่งฉันเดาว่ามันต้องการใช้แฮชที่ตรงกัน)

CREATE TABLE tinytable
  (
     id        INT PRIMARY KEY IDENTITY(1, 1),
     foreignId INT NOT NULL
  )

CREATE TABLE smalltable
  (
     id     INT PRIMARY KEY IDENTITY(1, 1),
     GuidId UNIQUEIDENTIFIER NOT NULL DEFAULT NEWID(),
     tinyId INT NOT NULL,
     Magic  NVARCHAR(max) NOT NULL DEFAULT ''
  )

CREATE TABLE bigtable
  (
     id          INT PRIMARY KEY IDENTITY(1, 1),
     CreatedUtc  DATETIME NOT NULL DEFAULT GETUTCDATE(),
     smallGuidId UNIQUEIDENTIFIER NOT NULL
  )

INSERT tinytable
       (foreignId)
VALUES(7)

INSERT smalltable
       (tinyId)
VALUES(1)

-- make a million rows 
DECLARE @i INT;

SET @i=20;

INSERT bigtable
       (smallGuidId)
SELECT GuidId
FROM   smalltable;

WHILE @i > 0
  BEGIN
      INSERT bigtable
             (smallGuidId)
      SELECT smallGuidId
      FROM   bigtable;

      SET @i=@i - 1;
  END 

ฉันได้ทดสอบกับ SQL 2005, 2008 และ 2008R2 ด้วยผลลัพธ์เดียวกัน

คำตอบ:


32

ฉันเห็นด้วยกับคำตอบของ Martin Smith แต่ปัญหาไม่ใช่เพียงหนึ่งในสถิติเท่านั้น สถิติสำหรับคอลัมน์ foreignId (สมมติว่าเปิดใช้งานสถิติอัตโนมัติ) อย่างถูกต้องแสดงว่าไม่มีแถวสำหรับค่า 3 (มีเพียงอันเดียวมีค่า 7):

DBCC SHOW_STATISTICS (tinytable, foreignId) WITH HISTOGRAM

เอาท์พุทสถิติ

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

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

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

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

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


21

ปัญหาพื้นฐานที่นี่คือหนึ่งในสถิติ

สำหรับเคียวรีทั้งคู่การนับแถวโดยประมาณแสดงให้เห็นว่าเชื่อว่าสุดท้ายSELECTจะส่งกลับ 1,048,580 แถว (จำนวนแถวเท่ากันโดยประมาณที่มีอยู่bigtable) แทนที่จะเป็น 0 ที่เป็นจริง

ทั้งสองJOINเงื่อนไขของคุณตรงกันและจะรักษาแถวทั้งหมดไว้ พวกเขาท้ายถูกกำจัดเพราะแถวเดียวในtinytableไม่ตรงกับภาคt.foreignId=3แสดง

ถ้าคุณวิ่ง

SELECT * 
FROM tinytable t  
WHERE t.foreignId=3  AND id=1 

และดูที่จำนวนแถวโดยประมาณมันเป็น1มากกว่า0และข้อผิดพลาดนี้แพร่กระจายทั่วทั้งแผน tinytableปัจจุบันมี 1 แถว สถิติจะไม่ได้รับการคอมไพล์ใหม่สำหรับตารางนี้จนกว่าการแก้ไข 500 แถวเกิดขึ้นดังนั้นสามารถเพิ่มแถวที่ตรงกันและจะไม่ทริกเกอร์การคอมไพล์ใหม่

สาเหตุที่การสั่งซื้อเข้าร่วมเปลี่ยนแปลงเมื่อคุณเพิ่มส่วนORDER BYคำสั่งและมีvarchar(max)คอลัมน์อยู่ในsmalltableนั้นเนื่องจากมันประมาณว่าvarchar(max)คอลัมน์จะเพิ่มจำนวนแถวเป็น 4,000 ไบต์โดยเฉลี่ย คูณที่ออกโดย 1048580 แถวและมันหมายความว่าการดำเนินการจัดเรียงจะต้องประมาณ 4GB จึงสมเหตุสมผลตัดสินใจที่จะทำดำเนินการก่อนSORTJOIN

คุณสามารถบังคับให้ORDER BYข้อความค้นหาใช้ORDER BYยุทธศาสตร์การเข้าร่วมที่ไม่ได้ใช้โดยใช้คำแนะนำดังต่อไปนี้

SELECT *
FROM   tinytable t /* one narrow row */
       INNER MERGE JOIN smalltable s /* one narrow row */
                        INNER LOOP JOIN bigtable b
                          ON b.smallGuidId = s.GuidId /* a million narrow rows */
         ON t.id = s.tinyId
WHERE  t.foreignId = 3 /* doesn't match */
ORDER  BY b.CreatedUtc
OPTION (MAXDOP 1) 

แผนแสดงตัวดำเนินการเรียงลำดับที่มีค่าใช้จ่ายทรีย่อยโดยประมาณของ12,000จำนวนแถวโดยประมาณที่เกือบจะผิดพลาดและขนาดข้อมูลโดยประมาณ

วางแผน

BTW ฉันไม่พบการแทนที่UNIQUEIDENTIFIERคอลัมน์ด้วยจำนวนเต็มที่เปลี่ยนแปลงสิ่งต่าง ๆ ในการทดสอบของฉัน


2

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

และนี่คือแบบสอบถาม "เร็ว": ป้อนคำอธิบายรูปภาพที่นี่

ดูที่ - ทำงานร่วมกันแบบสอบถามแรกคือ ~ แพงกว่า 33x (อัตราส่วน 97: 3) SQL กำลังปรับเคียวรีแรกให้เรียงลำดับ BigTable ตามวันที่และเวลาจากนั้นเรียกใช้การวนรอบ "ค้นหา" ขนาดเล็กบน SmallTable & TinyTable โดยเรียกใช้พวกเขา 1 ล้านครั้งในแต่ละครั้ง (คุณสามารถโฮเวอร์เหนือไอคอน ดังนั้นการเรียงลำดับ (27%) และ 2 x 1 ล้าน "ค้นหา" บนโต๊ะเล็ก ๆ (23% และ 46%) จึงเป็นกลุ่มคำถามที่มีราคาแพงจำนวนมาก ในการเปรียบเทียบการORDER BYค้นหาที่ไม่ได้ดำเนินการสแกนทั้งหมด 3 รายการ

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


2

สิ่งที่เกิดขึ้นคือ SQL กำลังตัดสินใจรันคำสั่งก่อนการ จำกัด

ลองสิ่งนี้:

SELECT *
(
SELECT * 
FROM tinytable t
    INNER JOIN smalltable s on t.id=s.tinyId
    INNER JOIN bigtable b on b.smallGuidId=s.GuidId
WHERE t.foreignId=3
) X
ORDER BY b.CreatedUtc

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

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

EXEC [sp_MSforeachtable] @command1="RAISERROR('UPDATE STATISTICS(''?'') ...',10,1) WITH NOWAIT UPDATE STATISTICS ? "

EXEC [sp_MSforeachtable] @command1="RAISERROR('DBCC DBREINDEX(''?'') ...',10,1) WITH NOWAIT DBCC DBREINDEX('?')"

EXEC [sp_MSforeachtable] @command1="RAISERROR('UPDATE STATISTICS(''?'') ...',10,1) WITH NOWAIT UPDATE STATISTICS ? "

1

คุณควรเพิ่มดัชนีสำหรับการสั่งซื้อของคุณตามฟิลด์และคุณจะเห็นว่าความเร็วจะเพิ่มขึ้น ดู/programming/1716798/sql-server-2008-ordering-by-datetime-is-too-slow

ลองฉันไม่คิดว่าคุณเดาว่ามันจะทำให้สิ่งต่าง ๆ ช้าลงเท่านั้นถูกต้อง

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