บังคับให้ Flow Distinct


19

ฉันมีโต๊ะแบบนี้:

CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1) PRIMARY KEY,
    ObjectId INT NOT NULL
)

การติดตามการอัปเดตพื้นฐานไปยังวัตถุที่มี ID เพิ่มขึ้น

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

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

SELECT DISTINCT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId

ที่ไหน @fromUpdateIdพารามิเตอร์กระบวนงานที่เก็บไว้

ด้วยแผนของ:

SELECT <- TOP <- Hash match (flow distinct, 100 rows touched) <- Index seek

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

เคล็ดลับนี้ยังส่งผลในแผนคิวรีแบบเดียวกัน (ด้วย TOP ที่ซ้ำซ้อน):

WITH ids AS
(
    SELECT ObjectId
    FROM Updates
    WHERE UpdateId > @fromUpdateId
    ORDER BY UpdateId OFFSET 0 ROWS
)
SELECT DISTINCT TOP 100 ObjectId FROM ids

แม้ว่าฉันไม่แน่ใจ (และไม่สงสัย) หากรับประกันการสั่งซื้ออย่างแท้จริง

หนึ่งแบบสอบถามที่ฉันหวังว่า SQL Server จะฉลาดพอที่จะทำให้มันง่ายขึ้น แต่มันก็จบลงด้วยการสร้างแผนการสืบค้นที่แย่มาก:

SELECT TOP 100 ObjectId
FROM Updates
WHERE UpdateId > @fromUpdateId
GROUP BY ObjectId
ORDER BY MIN(UpdateId)

ด้วยแผนของ:

SELECT <- Top N Sort <- Hash Match aggregate (50,000+ rows touched) <- Index Seek

ฉันกำลังพยายามหาวิธีในการสร้างแผนที่ดีที่สุดด้วยการค้นหาดัชนีUpdateIdและลำดับการไหลที่แตกต่างกันเพื่อลบรายการที่ซ้ำกันObjectId s ความคิดใด ๆ

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

@fromUpdateIdสมมติว่าผู้ใช้นี้มีวิธีการหาที่เหมาะสมต่อไปอีก ไม่จำเป็นต้องส่งคืนในแบบสอบถามนี้

คำตอบ:


15

เพิ่มประสิทธิภาพ SQL Server ไม่สามารถสร้างแผนการดำเนินการที่คุณมีอยู่หลังจากที่มีการรับประกันที่คุณต้องการเพราะแตกต่างแฮ Match ไหลผู้ประกอบการไม่ได้สั่งการรักษา

แม้ว่าฉันไม่แน่ใจ (และไม่สงสัย) หากรับประกันการสั่งซื้ออย่างแท้จริง

คุณอาจสังเกตการเก็บรักษาคำสั่งในหลายกรณี แต่นี่คือรายละเอียดการใช้งาน ไม่มีการรับประกันดังนั้นคุณไม่สามารถเชื่อถือได้ และเช่นเคยคำสั่งซื้อการนำเสนอสามารถรับประกันได้ในระดับสูงสุดเท่านั้นORDER BYประโยคเท่านั้น

ตัวอย่าง

สคริปต์ด้านล่างแสดงว่า Hash Match Flow Distinct ไม่รักษาลำดับ ตั้งค่าตารางที่เป็นปัญหาด้วยหมายเลขที่ตรงกัน 1-50,000 ในทั้งสองคอลัมน์:

IF OBJECT_ID(N'dbo.Updates', N'U') IS NOT NULL
    DROP TABLE dbo.Updates;
GO
CREATE TABLE Updates
(
    UpdateId INT NOT NULL IDENTITY(1,1),
    ObjectId INT NOT NULL,

    CONSTRAINT PK_Updates_UpdateId PRIMARY KEY (UpdateId)
);
GO
INSERT dbo.Updates (ObjectId)
SELECT TOP (50000)
    ObjectId =
        ROW_NUMBER() OVER (
            ORDER BY C1.[object_id]) 
FROM sys.columns AS C1
CROSS JOIN sys.columns AS C2
ORDER BY
    ObjectId;

แบบสอบถามทดสอบคือ:

DECLARE @Rows bigint = 50000;

-- Optimized for 1 row, but will be 50,000 when executed
SELECT DISTINCT TOP (@Rows)
    U.ObjectId 
FROM dbo.Updates AS U
WHERE 
    U.UpdateId > 0
OPTION (OPTIMIZE FOR (@Rows = 1));

แผนโดยประมาณแสดงดัชนีการค้นหาและการไหลที่แตกต่าง:

แผนโดยประมาณ

ดูเหมือนว่าคำสั่งจะเริ่มต้นด้วย:

เริ่มต้นผลลัพธ์

... แต่ค่าที่ต่ำกว่านั้นจะเริ่ม 'หายไป':

ลายพังทลาย

... และในที่สุด:

ความโกลาหลแตกออก

คำอธิบายในกรณีนี้คือตัวดำเนินการแฮชรั่วไหล:

แผนการหลังการดำเนินการ

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


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


11

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

ฉันเข้าหาปัญหานี้โดยพยายามคิดถึงการรวมกันของคอลัมน์ที่ฉันสามารถทำได้ORDER BYและได้ผลลัพธ์ที่ถูกต้องหลังจากนำDISTINCTไปใช้กับพวกเขา ค่าต่ำสุดของUpdateIdต่อObjectIdพร้อมกับObjectIdเป็นหนึ่งในการรวมกันดังกล่าว อย่างไรก็ตามการขอค่าต่ำสุดโดยตรงUpdateIdดูเหมือนจะส่งผลให้อ่านแถวทั้งหมดจากตาราง เราสามารถขอค่าขั้นต่ำของการUpdateIdเข้าร่วมกับตารางอื่นโดยอ้อมได้ แนวคิดคือการสแกนUpdatesตารางตามลำดับโยนแถวใด ๆ ที่UpdateIdไม่ใช่ค่าต่ำสุดสำหรับแถวObjectIdนั้นและเก็บ 100 แถวแรกไว้ จากคำอธิบายของการกระจายข้อมูลของคุณเราไม่จำเป็นต้องโยนแถวจำนวนมากออกไป

สำหรับการเตรียมข้อมูลฉันใส่ 1 ล้านแถวในตารางที่มี 2 แถวสำหรับแต่ละ ObjectId ที่แตกต่างกัน:

INSERT INTO Updates WITH (TABLOCK)
SELECT t.RN / 2
FROM 
(
    SELECT TOP 1000000 -1 + ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) t;

CREATE INDEX IX On Updates (Objectid, UpdateId);

ดัชนีที่ไม่ได้คลัสเตอร์ObjectidและUpdateIdเป็นสิ่งสำคัญ จะช่วยให้เราได้อย่างมีประสิทธิภาพโยนออกแถวที่ไม่ได้มีขั้นต่ำต่อUpdateId Objectidมีหลายวิธีในการเขียนแบบสอบถามที่ตรงกับคำอธิบายข้างต้น นี่เป็นวิธีหนึ่งในการใช้NOT EXISTS:

DECLARE @fromUpdateId INT = 9999;
SELECT ObjectId
FROM (
    SELECT DISTINCT TOP 100 u1.UpdateId, u1.ObjectId
    FROM Updates u1
    WHERE UpdateId > @fromUpdateId
    AND NOT EXISTS (
        SELECT 1
        FROM Updates u2
        WHERE u2.UpdateId > @fromUpdateId
        AND u1.ObjectId = u2.ObjectId
        AND u2.UpdateId < u1.UpdateId
    )
    ORDER BY u1.UpdateId, u1.ObjectId
) t;

นี่คือภาพของแผนแบบสอบถาม :

แผนแบบสอบถาม

ในกรณีที่ดีที่สุด SQL Server จะทำ 100 ดัชนีค้นหาเทียบกับดัชนี nonclustered เพื่อจำลองการรับเคราะห์ร้ายฉันเปลี่ยนคิวรีเพื่อส่งคืน 5,000 แถวแรกให้กับลูกค้า ที่ส่งผลให้ดัชนีพยายาม 9999 จึงเป็นเหมือนได้รับค่าเฉลี่ยของ 100 ObjectIdแถวต่อที่แตกต่างกัน นี่คือผลลัพธ์จากSET STATISTICS IO, TIME ON:

ตาราง 'อัพเดต' สแกนนับ 10,000, อ่านโลจิคัล 31900, อ่านฟิสิคัล 0

เวลาดำเนินการของ SQL Server: เวลา CPU = 31 ms, เวลาที่ผ่านไป = 42 ms


9

ฉันชอบคำถาม - Flow Distinct เป็นหนึ่งในผู้ให้บริการที่ฉันชอบ

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

ตามหลักวิชา FD สามารถร้องขอ 100 แถวจาก Seek และสร้างมันตามลำดับที่ต้องการ

คำแนะนำการสืบค้นOPTION (FAST 1, MAXDOP 1)อาจช่วยได้เพราะจะหลีกเลี่ยงการได้รับแถวมากกว่าที่ต้องการจากผู้ดำเนินการ Seek มันรับประกันหรือไม่ ไม่มาก มันยังคงสามารถตัดสินใจดึงหน้าของแถวในแต่ละครั้งหรืออะไรทำนองนั้น

ฉันคิดว่าเวอร์ชันOPTION (FAST 1, MAXDOP 1)ของคุณOFFSETจะให้ความมั่นใจกับคุณมากเกี่ยวกับการสั่งซื้อ แต่ก็ไม่รับประกัน


ดังที่ฉันเข้าใจแล้วปัญหาคือว่าตัวดำเนินการ Flow Distinct ใช้ตารางแฮชที่สามารถกระจายไปยังดิสก์ เมื่อมีการรั่วไหลแถวที่สามารถประมวลผลโดยใช้ส่วนที่ยังคงอยู่ใน RAM จะถูกประมวลผลทันที แต่แถวอื่น ๆ จะไม่ถูกประมวลผลจนกว่าข้อมูลที่รั่วไหลจะถูกอ่านจากดิสก์ จากสิ่งที่ฉันสามารถบอกได้โอเปอเรเตอร์ที่ใช้ตารางแฮช (เช่นการเข้าร่วมแฮช) ไม่รับประกันว่าจะรักษาความสงบเรียบร้อยเนื่องจากพฤติกรรมการหก
sam.bishop

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