ข้อความค้นหาย่อยที่มีประสิทธิภาพต่ำและมีการเปรียบเทียบวันที่


15

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

สำหรับแบบสอบถามนี้ผลลัพธ์จะต้อง:

  • รวมเฉพาะบันทึกเหล่านั้นภายในช่วงวันที่ที่กำหนด
  • รวมการนับของระเบียนก่อนหน้าทั้งหมดไม่รวมระเบียนปัจจุบันโดยไม่คำนึงถึงช่วงวันที่

โครงสร้างตารางพื้นฐาน

Activity
======================
Id int Identifier
Address varchar(25)
ActionDate datetime2
Process varchar(50)
-- 7 other columns

ตัวอย่างข้อมูล

Id  Address     ActionDate (Time part excluded for simplicity)
===========================
99  000         2017-05-30
98  111         2017-05-30
97  000         2017-05-29
96  000         2017-05-28
95  111         2017-05-19
94  222         2017-05-30

ผลลัพธ์ที่คาดหวัง

สำหรับช่วงวันที่2017-05-29ถึง2017-05-30

Id  Address     ActionDate    PriorCount
=========================================
99  000         2017-05-30    2  (3 total, 2 prior to ActionDate)
98  111         2017-05-30    1  (2 total, 1 prior to ActionDate)
94  222         2017-05-30    0  (1 total, 0 prior to ActionDate)
97  000         2017-05-29    1  (3 total, 1 prior to ActionDate)

บันทึก 96 และ 95 ไม่รวมอยู่ในผลลัพธ์ แต่รวมอยู่ในPriorCountแบบสอบถามย่อย

คำค้นหาปัจจุบัน

select 
    *.a
    , ( select count(*) 
        from Activity
        where 
            Activity.Address = a.Address
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc

ดัชนีปัจจุบัน

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON [dbo].[Activity]
(
    [ActionDate] ASC
)
INCLUDE ([Address]) WITH (
    PAD_INDEX = OFF, 
    STATISTICS_NORECOMPUTE = OFF, 
    SORT_IN_TEMPDB = OFF, 
    DROP_EXISTING = OFF, 
    ONLINE = OFF, 
    ALLOW_ROW_LOCKS = ON, 
    ALLOW_PAGE_LOCKS = ON
)

คำถาม

  • กลยุทธ์ใดบ้างที่สามารถใช้ในการปรับปรุงประสิทธิภาพของแบบสอบถามนี้

แก้ไข 1
ในการตอบคำถามที่ฉันสามารถแก้ไขได้ใน DB: ฉันสามารถแก้ไขดัชนีได้ไม่ใช่โครงสร้างของตาราง

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

แก้ไข 3
ดัชนี Spool Joe Obbish (คำตอบที่ยอมรับ) พบว่าเป็นปัญหา เมื่อฉันเพิ่มเข้าไปใหม่nonclustered index [xyz] on [Activity] (Address) include (ActionDate)เวลาค้นหาจะลดลงจากขึ้นไปหนึ่งนาทีถึงน้อยกว่าหนึ่งวินาทีโดยไม่ใช้ตารางชั่วคราว (ดูแก้ไข 2)

คำตอบ:


17

ด้วยข้อกำหนดดัชนีที่คุณมีIDX_my_nmeSQL Server จะสามารถค้นหาการใช้ActionDateคอลัมน์ แต่ไม่ได้อยู่กับAddressคอลัมน์ ดัชนีมีคอลัมน์ทั้งหมดที่จำเป็นในการครอบคลุมแบบสอบถามย่อย แต่มีแนวโน้มว่าจะไม่ได้เลือกอย่างมากสำหรับแบบสอบถามย่อย สมมติว่าเกือบทั้งหมดของข้อมูลในตารางมีค่าของก่อนหน้านี้กว่าActionDate '2017-05-30'การค้นหาActionDate < '2017-05-30'จะส่งคืนแถวเกือบทั้งหมดจากดัชนีซึ่งจะถูกกรองเพิ่มเติมหลังจากแถวถูกดึงออกมาจากดัชนี หากเคียวรีของคุณส่งคืน 200 แถวคุณอาจทำการสแกนดัชนีเกือบเต็ม 200 รายการIDX_my_nmeซึ่งหมายความว่าคุณจะอ่านประมาณ 50000 * 200 = 10 ล้านแถวจากดัชนี

อาจเป็นไปได้ว่าAddressการสืบค้นจะมีความละเอียดมากยิ่งขึ้นสำหรับคำถามย่อยของคุณแม้ว่าคุณจะไม่ได้ให้ข้อมูลเชิงสถิติเกี่ยวกับการสืบค้นให้เรา แต่สมมติว่าคุณสร้างดัชนีเพียงAddressและโต๊ะของคุณมีค่า 10k Addressไม่ซ้ำกันสำหรับ ด้วยดัชนีใหม่ SQL Server จะต้องค้นหาเพียง 5 แถวจากดัชนีสำหรับการดำเนินการย่อยแต่ละแบบสอบถามดังนั้นคุณจะอ่านแถวประมาณ 200 * 5 = 1,000 แถวจากดัชนี

ฉันกำลังทดสอบกับ SQL Server 2016 ดังนั้นอาจมีความแตกต่างของไวยากรณ์เล็กน้อย ด้านล่างเป็นข้อมูลตัวอย่างบางส่วนที่ฉันใช้สมมติฐานที่คล้ายกันกับด้านบนสำหรับการแจกแจงข้อมูล:

CREATE TABLE #Activity (
    Id int NOT NULL,
    [Address] varchar(25) NULL,
    ActionDate datetime2 NULL,
    FILLER varchar(100),
    PRIMARY KEY (Id)
);

INSERT INTO #Activity WITH (TABLOCK)
SELECT TOP (50000) -- 50k total rows
x.RN
, x.RN % 10000 -- 10k unique addresses
, DATEADD(DAY, x.RN / 100, '20160201') -- 100 rows per day
, REPLICATE('Z', 100)
FROM
(
    SELECT ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) RN
    FROM master..spt_values t1
    CROSS JOIN master..spt_values t2
) x;

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([ActionDate] ASC) INCLUDE ([Address]);

ฉันได้สร้างดัชนีของคุณตามที่อธิบายไว้ในคำถาม ฉันกำลังทดสอบกับแบบสอบถามนี้ซึ่งส่งคืนข้อมูลเดียวกับคำถาม:

select 
    a.*
    , ( select count(*) 
        from #Activity Activity
        where 
            Activity.[Address] = a.[Address]
            and Activity.ActionDate < a.ActionDate
    ) as PriorCount
from #Activity a
where a.ActionDate between '2017-05-29' and '2017-05-30'
order by a.ActionDate desc;

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

สปูลดัชนี

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

แสวงหาดัชนีที่ไม่ดี

อย่าหลงกลโดยการมองหาดัชนีอย่างง่าย SQL Server อ่านดัชนีเกือบ 10 ล้านแถว:

แถว 10M จากดัชนี

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

CREATE NONCLUSTERED INDEX [IDX_my_nme_2] ON #Activity
([Address] ASC) INCLUDE (ActionDate);

แผนคล้ายกับก่อนหน้านี้:

ค้นหาดัชนี

อย่างไรก็ตามด้วยดัชนีใหม่ SQL Server จะอ่าน 1,000 แถวจากดัชนีเท่านั้น 800 ของแถวจะถูกส่งคืนเพื่อนับ ดัชนีสามารถกำหนดให้เลือกได้มากกว่านี้ แต่อาจดีพอขึ้นอยู่กับการกระจายข้อมูลของคุณ

แสวงหาที่ดี

หากคุณไม่สามารถกำหนดดัชนีเพิ่มเติมใด ๆ ในตารางฉันจะพิจารณาใช้ฟังก์ชันหน้าต่าง ดูเหมือนว่าจะทำงานต่อไปนี้:

SELECT t.*
FROM
(
    select 
        a.*
        , -1 + ROW_NUMBER() OVER (PARTITION BY [Address] ORDER BY ActionDate) PriorCount
    from #Activity a
) t
where t.ActionDate between '2017-05-29' and '2017-05-30'
order by t.ActionDate desc;

แบบสอบถามนั้นทำการสแกนข้อมูลเพียงครั้งเดียว แต่เป็นการเรียงลำดับที่มีราคาแพงและคำนวณROW_NUMBER()ฟังก์ชันสำหรับทุกแถวในตารางดังนั้นจึงรู้สึกว่ามีงานพิเศษทำอยู่ที่นี่:

เรียงลำดับไม่ดี

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

CREATE NONCLUSTERED INDEX [IDX_my_nme] ON #Activity
([Address], [ActionDate]) INCLUDE (FILLER);

นั่นทำให้การเรียงลำดับสิ้นสุดลงซึ่งจะมีราคาถูกกว่ามาก:

การเรียงลำดับที่ดี

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


1
ดัชนีสปูลที่คุณพบมีปัญหา เมื่อฉันเพิ่มเข้าไปใหม่nonclustered index [xyz] on [Activity] (Address) include (ActionDate)เวลาค้นหาจะลดลงจากขึ้นไปหนึ่งนาทีหรือน้อยกว่าหนึ่งวินาที +10 ถ้าทำได้ ขอบคุณ!
Metro Smurf
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.