Tôi có một [UserActivity]
bảng cơ bản để ghi lại ActivityTypeId
mỗi UserId
và ActivityDate
tại đó Hoạt động xảy ra.
Tôi viết một truy vấn / thủ tục lưu trữ cho phép đầu vào của @UserId
, @ForTypeId
cũng như các @DurationInterval
và @DurationIncrement
kết quả trả lại tự động dựa trên N số giây / phút / giờ / ngày / tháng / năm. Cho rằng datepart
đối số bên trong DATEADD/DATEDIFF
không cho phép tham số, tôi đã phải hoàn nguyên một chút mánh khóe để có được kết quả mong muốn trong WHERE
mệnh đề.
Ban đầu tôi đã viết truy vấn bằng cách sử dụng DATEDIFF
, nhưng ngay sau khi viết và xem qua kế hoạch thực hiện, tôi nhớ rằng đó không phải là chức năng SARGable (cùng với thực tế là các mức chính xác có thể cung cấp cho một số ngày rơi vào Năm nhuận). Vì vậy, tôi đã viết lại truy vấn để sử dụng DATEPART
suy nghĩ rằng tôi sẽ đạt được một tìm kiếm chỉ mục thay vì quét chỉ mục và thường hoạt động tốt hơn.
Thật không may, tôi đã phát hiện ra rằng việc viết truy vấn sẽ DATEADD
mang lại kết quả tương tự: quá trình quét chỉ mục đang diễn ra và trình tối ưu hóa truy vấn không tận dụng chỉ mục không được nhóm [ActivityDate]
.
Tôi đọc bài viết trên blog Aaron Bertrand, "Hiệu suất Surprises và Giả định: DATEADD" , và thực hiện những thay đổi ông mô tả để CONVERT
các DATEADD
phần vào tương đương với datetime2
định nghĩa cột do lừa đảo kỳ lạ liên quan đến datetime2
. Tuy nhiên, vấn đề vẫn còn hiện diện ngay cả sau khi làm như vậy.
Để minh họa rõ hơn cho kịch bản, đây là một định nghĩa bảng so sánh.
DROP TABLE IF EXISTS [dbo].[UserActivity]
IF OBJECT_ID('[dbo].[UserActivity]', 'U') IS NULL
BEGIN
CREATE TABLE [dbo].[UserActivity] (
[UserId] [int] NOT NULL
,[UserActivityId] [bigint] IDENTITY(1,1) NOT NULL
,[ActivityTypeId] [tinyint] NOT NULL
,[ActivityDate] [datetime2](0) NOT NULL CONSTRAINT [DF_UserActivity_ActivityDate] DEFAULT GETDATE()
,CONSTRAINT [PK_UserActivity] PRIMARY KEY CLUSTERED ([UserActivityId] ASC)
,INDEX [IX_UserActivity_UserId] NONCLUSTERED ([UserId] ASC)
,INDEX [IX_UserActivity_ActivityTypeId] NONCLUSTERED ([ActivityTypeId] ASC)
,INDEX [IX_UserActivity_ActivityDate] NONCLUSTERED ([ActivityDate] ASC)
)
END;
GO
Nhập bảng với dữ liệu giả theo cách đệ quy cho 5 người dùng khác nhau với ngẫu nhiên ActivityTypeId
từ 1 đến 10 với mới ActivityDate
4 phút một lần.
DECLARE @UserId int = (SELECT ISNULL((SELECT TOP (1) [UserId] + 1 FROM [dbo].[UserActivity] ORDER BY [UserId] DESC), 1))
;WITH [UserActivitySeed] AS (
SELECT
CONVERT(datetime2(0), '01/01/2018') AS 'ActivityDate'
UNION ALL
SELECT
DATEADD(minute, 4, [ActivityDate])
FROM
[UserActivitySeed]
WHERE
[ActivityDate] < '2018-04-01')
INSERT INTO [dbo].[UserActivity] ([UserId], [ActivityTypeId], [ActivityDate])
SELECT
@UserId
,ABS(CHECKSUM(NEWID()) % 9) + 1
,[ActivityDate]
FROM
[UserActivitySeed] OPTION (MAXRECURSION 32767);
GO 5
ALTER INDEX ALL ON [dbo].[UserActivity] REBUILD;
Dưới đây là truy vấn đầu tiên tôi viết với DATEDIFF
. Lưu ý Tôi đang loại trừ các biến vị ngữ @UserId
và các biến @ForTypeId
vị ngữ một cách có chủ ý để tránh các tra cứu chính đó và giảm nhiễu trong các kế hoạch được đính kèm.
Như bạn sẽ tìm thấy trên PasteThePlan cho truy vấn này , nó đang thực hiện quét chỉ mục như mong đợi mà DATEDIFF
không phải là SARGable.
DECLARE @UserId int = 1
DECLARE @ForTypeId int = 3
DECLARE @DurationInterval varchar(6) = 'hour'
DECLARE @DurationIncrement int = 1
SELECT
COUNT(UA.[UserActivityId]) AS 'ActivityTypeCount'
FROM
[dbo].[UserActivity] UA
WHERE
-- Exclude the @UserId and @ForTypeId predicates.
-- UA.[UserId] = @UserId
-- AND UA.[ActivityTypeId] = @ForTypeId
-- AND
CASE
WHEN @DurationInterval IN ('year', 'yy', 'yyyy') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE()) / 3600.0 / 24.0 / 365.25
WHEN @DurationInterval IN ('month', 'mm', 'm') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE()) / 3600.0 / 24.0 / 365.25 * 12
WHEN @DurationInterval IN ('day', 'dd', 'd') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE()) / 3600.0 / 24.0
WHEN @DurationInterval IN ('hour', 'hh') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE()) / 3600.0
WHEN @DurationInterval IN ('minute', 'mi', 'n') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE()) / 60.0
WHEN @DurationInterval IN ('second', 'ss', 's') THEN DATEDIFF(SECOND, UA.[ActivityDate], GETDATE())
END < @DurationIncrement
Dưới đây là DATEADD
truy vấn. DánThePlan ở đây. Thật không may, một tìm kiếm chỉ mục không xảy ra. Đây có thể là một giả định không chính xác về phía tôi, nhưng tôi bối rối về lý do tại sao nó không xảy ra.
DECLARE @UserId int = 1
DECLARE @ForTypeId int = 3
DECLARE @DurationInterval varchar(6) = 'hour'
DECLARE @DurationIncrement int = 1
SELECT
COUNT(UA.[UserActivityId]) AS 'ActivityTypeCount'
FROM
[dbo].[UserActivity] UA
WHERE
-- Exclude the @UserId and @ForTypeId predicates.
-- UA.[UserId] = @UserId
-- AND UA.[ActivityTypeId] = @ForTypeId
-- AND
(
(@DurationInterval IN ('year', 'yy', 'yyyy') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(YEAR, -@DurationIncrement, GETDATE())))
OR
(@DurationInterval IN ('month', 'mm', 'm') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(MONTH, -@DurationIncrement, GETDATE())))
OR
(@DurationInterval IN ('day', 'dd', 'd') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(DAY, -@DurationIncrement, GETDATE())))
OR
(@DurationInterval IN ('hour', 'hh') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(HOUR, -@DurationIncrement, GETDATE())))
OR
(@DurationInterval IN ('minute', 'mi', 'n') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(MINUTE, -@DurationIncrement, GETDATE())))
OR
(@DurationInterval IN ('second', 'ss', 's') AND UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(SECOND, -@DurationIncrement, GETDATE())))
)
Nguyên nhân của điều này là gì? Có phải hành vi tôi đang thấy là kết quả của việc tôi sử dụng để OR
phủ nhận bất kỳ tiềm năng nào để nó thậm chí có thể sử dụng chỉ mục không? Tôi đang nhìn một cái gì đó rõ ràng ở đây?
CẬP NHẬT: Câu hỏi thứ hai của tôi ở trên dẫn tôi thực hiện một truy vấn nêu trên các OR
hoạt động. Truy vấn đã thực hiện tìm kiếm chỉ mục, do đó, một cái gì đó đang xảy ra trong những so sánh này mà SQL Server không thích. DánThePlan ở đây.
DECLARE @DurationIncrement int = 1
SELECT
COUNT(UA.[UserActivityId]) AS 'ActivityTypeCount'
FROM
[dbo].[UserActivity] UA
WHERE
UA.[ActivityDate] > CONVERT(datetime2(0), DATEADD(HOUR, -@DurationIncrement, GETDATE()))
CẬP NHẬT: Giải pháp được chia sẻ ở đây.
WHERE
mệnh đề xung quanh như vậy, nó đánh vào chỉ mục không được phân cụm một cách thích hợp. Tôi đã cập nhật OP của mình với truy vấn chính xác. Cảm ơn ngài.