Truy vấn con thực hiện kém với so sánh ngày


15

Khi sử dụng truy vấn con để tìm tổng số tất cả các bản ghi trước đó với trường khớp, hiệu suất rất tệ trên một bảng có ít nhất là 50 nghìn bản ghi. Không có truy vấn con, truy vấn sẽ thực hiện trong vài mili giây. Với truy vấn con, thời gian thực hiện lên đến một phút.

Đối với truy vấn này, kết quả phải:

  • Chỉ bao gồm những hồ sơ trong một phạm vi ngày nhất định.
  • Bao gồm số lượng của tất cả các hồ sơ trước đó, không bao gồm hồ sơ hiện tại, bất kể phạm vi ngày.

Lược đồ bảng cơ bản

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

Dữ liệu mẫu

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

Kết quả dự kiến

Đối với phạm vi ngày 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)

Bản ghi 96 và 95 được loại trừ khỏi kết quả, nhưng được bao gồm trong PriorCounttruy vấn con

Truy vấn hiện tại

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

Chỉ số hiện tại

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
)

Câu hỏi

  • Những chiến lược nào có thể được sử dụng để cải thiện hiệu suất của truy vấn này?

Chỉnh sửa 1
Trả lời câu hỏi về những gì tôi có thể sửa đổi trên DB: Tôi có thể sửa đổi các chỉ mục, không chỉ là cấu trúc bảng.

Chỉnh sửa 2
Bây giờ tôi đã thêm một chỉ mục cơ bản trên Addresscột, nhưng điều đó dường như không cải thiện nhiều. Tôi hiện đang tìm thấy hiệu suất tốt hơn nhiều với việc tạo bảng tạm thời và chèn các giá trị mà không cần PriorCountcập nhật từng hàng với số lượng cụ thể của chúng.

Chỉnh sửa 3
Chỉ số Spool Joe Obbish (câu trả lời được chấp nhận) được tìm thấy là vấn đề. Khi tôi đã thêm một cái mới nonclustered index [xyz] on [Activity] (Address) include (ActionDate), thời gian truy vấn giảm dần từ một phút xuống dưới một giây mà không sử dụng bảng tạm thời (xem chỉnh sửa 2).

Câu trả lời:


17

Với định nghĩa chỉ mục mà bạn có IDX_my_nme , SQL Server sẽ có thể tìm kiếm bằng cách sử dụng ActionDatecột nhưng không phải với Addresscột. Chỉ mục chứa tất cả các cột cần thiết để bao gồm truy vấn con nhưng có thể nó không được chọn nhiều cho truy vấn phụ đó. Giả sử rằng hầu hết tất cả các dữ liệu trong bảng có ActionDategiá trị sớm hơn '2017-05-30'. Tìm kiếm ActionDate < '2017-05-30'sẽ trả về hầu hết tất cả các hàng từ chỉ mục, được lọc tiếp sau khi hàng được lấy từ chỉ mục. Nếu truy vấn của bạn trả về 200 hàng thì có thể bạn sẽ thực hiện gần 200 lần quét chỉ mục đầy đủ IDX_my_nme, điều đó có nghĩa là bạn sẽ đọc khoảng 50000 * 200 = 10 triệu hàng từ chỉ mục.

Có vẻ như việc tìm kiếm Addresssẽ có nhiều lựa chọn hơn cho truy vấn con của bạn, mặc dù bạn chưa cung cấp cho chúng tôi thông tin thống kê đầy đủ về truy vấn nên đó là một giả định về phía tôi. Tuy nhiên, giả sử rằng bạn chỉ tạo một chỉ mục Addressvà bảng của bạn có 10k giá trị duy nhất cho Address. Với chỉ mục mới, SQL Server sẽ chỉ cần tìm kiếm 5 hàng từ chỉ mục cho mỗi lần thực hiện truy vấn con, vì vậy bạn sẽ đọc khoảng 200 * 5 = 1000 hàng từ chỉ mục.

Tôi đang thử nghiệm với SQL Server 2016 vì vậy có thể có một số khác biệt nhỏ về cú pháp. Dưới đây là một số dữ liệu mẫu mà tôi đã đưa ra các giả định tương tự như trên để phân phối dữ liệu:

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]);

Tôi đã tạo chỉ mục của bạn như được mô tả trong câu hỏi. Tôi đang kiểm tra truy vấn này trả về cùng một dữ liệu trong câu hỏi:

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;

Tôi nhận được một chỉ số spool. Điều đó có nghĩa là ở mức cơ bản là trình tối ưu hóa truy vấn xây dựng một chỉ mục tạm thời đang hoạt động vì không có chỉ mục nào hiện có so với bảng là phù hợp.

chỉ mục

Các truy vấn vẫn kết thúc nhanh chóng cho tôi. Có lẽ bạn không nhận được tối ưu hóa bộ đệm chỉ mục trên hệ thống của mình hoặc có điều gì đó khác biệt về định nghĩa bảng hoặc truy vấn. Đối với mục đích giáo dục, tôi có thể sử dụng một tính năng không có giấy tờ OPTION (QUERYRULEOFF BuildSpool)để vô hiệu hóa bộ đệm chỉ mục. Đây là những gì kế hoạch trông như thế nào:

chỉ số xấu tìm kiếm

Đừng để bị lừa bởi sự xuất hiện của một tìm kiếm chỉ mục đơn giản. SQL Server đọc gần 10 triệu hàng từ chỉ mục:

10 triệu hàng từ chỉ mục

Nếu tôi sẽ chạy truy vấn nhiều lần thì có lẽ nó không có ý nghĩa đối với trình tối ưu hóa truy vấn để tạo một chỉ mục mỗi khi nó chạy. Tôi có thể tạo một chỉ mục trả trước sẽ được lựa chọn nhiều hơn cho truy vấn này:

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

Kế hoạch tương tự như trước đây:

chỉ số tìm kiếm

Tuy nhiên, với chỉ mục mới, SQL Server chỉ đọc 1000 hàng từ chỉ mục. 800 trong số các hàng được trả lại để được tính. Chỉ mục có thể được xác định là chọn lọc hơn nhưng điều này có thể đủ tốt tùy thuộc vào phân phối dữ liệu của bạn.

tìm kiếm tốt

Nếu bạn không thể xác định bất kỳ chỉ mục bổ sung nào trên bảng, tôi sẽ xem xét sử dụng các chức năng của cửa sổ. Sau đây có vẻ hoạt động:

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;

Truy vấn đó thực hiện một lần quét dữ liệu nhưng thực hiện một sắp xếp đắt tiền và tính toán ROW_NUMBER()hàm cho mỗi hàng trong bảng, do đó, có cảm giác như có một số công việc bổ sung được thực hiện ở đây:

sắp xếp xấu

Tuy nhiên, nếu bạn thực sự thích mẫu mã đó, bạn có thể xác định một chỉ mục để làm cho nó hiệu quả hơn:

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

Điều đó di chuyển sắp xếp về cuối sẽ ít tốn kém hơn nhiều:

loại tốt

Nếu không có điều này giúp thì bạn sẽ cần thêm thông tin vào câu hỏi, tốt nhất là bao gồm các kế hoạch thực hiện thực tế.


1
Chỉ số Spool bạn tìm thấy là vấn đề. Khi tôi thêm vào một cái mới nonclustered index [xyz] on [Activity] (Address) include (ActionDate), thời gian truy vấn giảm dần từ một phút xuống dưới một giây. +10 nếu tôi có thể. Cảm ơn!
Tàu điện ngầm Smurf
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.