เหตุใดการสแกนจึงเร็วกว่าการค้นหาคำนี้


30

ฉันสามารถสร้างปัญหาประสิทธิภาพการสืบค้นที่ฉันจะอธิบายได้อย่างไม่คาดคิด ฉันกำลังมองหาคำตอบที่มุ่งเน้นไปที่ภายใน

บนเครื่องของฉันเคียวรีต่อไปนี้ทำการสแกนดัชนีแบบคลัสเตอร์และใช้เวลาประมาณ 6.8 วินาทีของเวลา CPU:

SELECT ID1, ID2
FROM two_col_key_test WITH (FORCESCAN)
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

แบบสอบถามต่อไปนี้จะค้นหาดัชนีแบบคลัสเตอร์ (ข้อแตกต่างคือการลบFORCESCANคำใบ้ออก) แต่ใช้เวลา CPU ประมาณ 18.2 วินาที:

SELECT ID1, ID2
FROM two_col_key_test
WHERE ID1 NOT IN
(
N'1', N'2',N'3', N'4', N'5',
N'6', N'7', N'8', N'9', N'10',
N'11', N'12',N'13', N'14', N'15',
N'16', N'17', N'18', N'19', N'20'
)
AND (ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))
ORDER BY ID1, ID2 OFFSET 12000000 ROWS FETCH FIRST 1 ROW ONLY
OPTION (MAXDOP 1);

แผนแบบสอบถามคล้ายกันมาก สำหรับเคียวรีทั้งสองมีการอ่านแถว 120000001 จากดัชนีคลัสเตอร์:

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

ฉันอยู่ใน SQL Server 2017 CU 10 นี่คือรหัสในการสร้างและเติมtwo_col_key_testตาราง:

drop table if exists dbo.two_col_key_test;

CREATE TABLE dbo.two_col_key_test (
    ID1 NVARCHAR(50) NOT NULL,
    ID2 NVARCHAR(50) NOT NULL,
    FILLER NVARCHAR(50),
    PRIMARY KEY (ID1, ID2)
);

DROP TABLE IF EXISTS #t;

SELECT TOP (4000) 0 ID INTO #t
FROM master..spt_values t1
CROSS JOIN master..spt_values t2
OPTION (MAXDOP 1);


INSERT INTO dbo.two_col_key_test WITH (TABLOCK)
SELECT N'FILLER TEXT' + CASE WHEN ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) > 8000000 THEN N' 2' ELSE N'' END
, ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
, NULL
FROM #t t1
CROSS JOIN #t t2;

ฉันหวังว่าจะได้รับคำตอบที่มากกว่าการรายงานสแต็คการโทร ตัวอย่างเช่นฉันเห็นว่าsqlmin!TCValSSInRowExprFilter<231,0,0>::GetDataXใช้รอบ CPU มากขึ้นอย่างมากในการสืบค้นที่ช้าเมื่อเปรียบเทียบกับการตอบสนองที่รวดเร็ว:

perview

แทนที่จะหยุดตรงนั้นฉันอยากจะเข้าใจว่ามันคืออะไรและทำไมมันถึงมีความแตกต่างอย่างมากระหว่างสองข้อความค้นหา

เหตุใดเวลา CPU แตกต่างกันมากสำหรับทั้งสองคิวรี

คำตอบ:


31

เหตุใดเวลา CPU แตกต่างกันมากสำหรับทั้งสองคิวรี

แผนการสแกนจะประเมินเพรดิเคตที่ไม่สามารถกำหนดเป้าหมายได้ (ส่วนที่เหลือ) ต่อไปนี้สำหรับแต่ละแถว:

[two_col_key_test].[ID1]<>N'1' 
AND [two_col_key_test].[ID1]<>N'10' 
AND [two_col_key_test].[ID1]<>N'11' 
AND [two_col_key_test].[ID1]<>N'12' 
AND [two_col_key_test].[ID1]<>N'13' 
AND [two_col_key_test].[ID1]<>N'14' 
AND [two_col_key_test].[ID1]<>N'15' 
AND [two_col_key_test].[ID1]<>N'16' 
AND [two_col_key_test].[ID1]<>N'17' 
AND [two_col_key_test].[ID1]<>N'18' 
AND [two_col_key_test].[ID1]<>N'19' 
AND [two_col_key_test].[ID1]<>N'2' 
AND [two_col_key_test].[ID1]<>N'20' 
AND [two_col_key_test].[ID1]<>N'3' 
AND [two_col_key_test].[ID1]<>N'4' 
AND [two_col_key_test].[ID1]<>N'5' 
AND [two_col_key_test].[ID1]<>N'6' 
AND [two_col_key_test].[ID1]<>N'7' 
AND [two_col_key_test].[ID1]<>N'8' 
AND [two_col_key_test].[ID1]<>N'9' 
AND 
(
    [two_col_key_test].[ID1]=N'FILLER TEXT' 
    AND [two_col_key_test].[ID2]>=N'' 
    OR [two_col_key_test].[ID1]>N'FILLER TEXT'
)

สแกนที่เหลือ

แผนค้นหาดำเนินการค้นหาสองครั้ง:

Seek Keys[1]: 
    Prefix: 
    [two_col_key_test].ID1 = Scalar Operator(N'FILLER TEXT'), 
        Start: [two_col_key_test].ID2 >= Scalar Operator(N'')
Seek Keys[1]: 
    Start: [two_col_key_test].ID1 > Scalar Operator(N'FILLER TEXT')

... เพื่อให้ตรงกับส่วนนี้ของภาคแสดง:

(ID1 = N'FILLER TEXT' AND ID2 >= N'' OR (ID1 > N'FILLER TEXT'))

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

อย่างไรก็ตามความไม่เท่าเทียมกันแต่ละครั้งจะถูกแทนที่ด้วยการทดสอบสองชุดแยกกันน้อย OR กว่า :

([two_col_key_test].[ID1]<N'1' OR [two_col_key_test].[ID1]>N'1') 
AND ([two_col_key_test].[ID1]<N'10' OR [two_col_key_test].[ID1]>N'10') 
AND ([two_col_key_test].[ID1]<N'11' OR [two_col_key_test].[ID1]>N'11') 
AND ([two_col_key_test].[ID1]<N'12' OR [two_col_key_test].[ID1]>N'12') 
AND ([two_col_key_test].[ID1]<N'13' OR [two_col_key_test].[ID1]>N'13') 
AND ([two_col_key_test].[ID1]<N'14' OR [two_col_key_test].[ID1]>N'14') 
AND ([two_col_key_test].[ID1]<N'15' OR [two_col_key_test].[ID1]>N'15') 
AND ([two_col_key_test].[ID1]<N'16' OR [two_col_key_test].[ID1]>N'16') 
AND ([two_col_key_test].[ID1]<N'17' OR [two_col_key_test].[ID1]>N'17') 
AND ([two_col_key_test].[ID1]<N'18' OR [two_col_key_test].[ID1]>N'18') 
AND ([two_col_key_test].[ID1]<N'19' OR [two_col_key_test].[ID1]>N'19') 
AND ([two_col_key_test].[ID1]<N'2' OR [two_col_key_test].[ID1]>N'2') 
AND ([two_col_key_test].[ID1]<N'20' OR [two_col_key_test].[ID1]>N'20') 
AND ([two_col_key_test].[ID1]<N'3' OR [two_col_key_test].[ID1]>N'3') 
AND ([two_col_key_test].[ID1]<N'4' OR [two_col_key_test].[ID1]>N'4') 
AND ([two_col_key_test].[ID1]<N'5' OR [two_col_key_test].[ID1]>N'5') 
AND ([two_col_key_test].[ID1]<N'6' OR [two_col_key_test].[ID1]>N'6') 
AND ([two_col_key_test].[ID1]<N'7' OR [two_col_key_test].[ID1]>N'7') 
AND ([two_col_key_test].[ID1]<N'8' OR [two_col_key_test].[ID1]>N'8') 
AND ([two_col_key_test].[ID1]<N'9' OR [two_col_key_test].[ID1]>N'9')

หาที่เหลือ

เขียนใหม่แต่ละความไม่เท่าเทียมกันเช่น:

[ID1] <> N'1'  ->  [ID1]<N'1' OR [ID1]>N'1'

... ต่อต้านที่นี่ การเปรียบเทียบสตริงการรับรู้การเรียงมีราคาแพง การเพิ่มจำนวนการเปรียบเทียบเป็นสองเท่าอธิบายความแตกต่างส่วนใหญ่ในเวลา CPU ที่คุณเห็น

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

การสแกน

แสวงหา

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

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

โปรดทราบว่ารูปแบบการประมาณเชิงการนับดั้งเดิม ("ดั้งเดิม") เกิดขึ้นเพื่อเลือกการสแกนตามค่าเริ่มต้นสำหรับการสืบค้นนี้

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