如何优化取决于执行时间的 T-SQL 查询?
How to optimize T-SQL query which depends on execution time?
我有一个很大的(几百万行)table 系统事件。我必须获取最近的事件计数,但我不知道如何以正确的方式进行。
我创建了这个视图:
CREATE VIEW [dbo].[EventCounts]
AS
SELECT
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -15, GETDATE())
AND r.Timestamp <= GETDATE()) AS last15minEventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -30, GETDATE())
AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE())) AS from15to30EventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -60, GETDATE())
AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE())) AS from30to60EventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp <= DATEADD(MINUTE, -60, GETDATE())) AS olderThan60minEventCount
这个观点returns:
- < 15 分钟事件计数;
- 15 - 30 分钟的事件计数;
- 30 - 60 分钟的事件计数;
- 超过 60 分钟的事件计数。
现在我的代码在整个 table 上运行了 4 次,我想优化它。我不能使用预聚合,因为这取决于执行时间。由于同样的原因,我无法使用索引视图。
我想我可以在 Timestamp 列上添加非聚集索引,但代码仍然需要读取整个 table 得到这个计数,对吗?
你能建议如何优化我的查询吗?
P.S。 last15minEventCount、from15to30EventCount、from30to60EventCount - 所有事件的很小一部分。
一个建议,你可以改变方法,像下面这样写你的查询。
CREATE VIEW [dbo].[EventCounts]
AS
WITH Boundary AS (
SELECT Dateadd(MINUTE, -15, Getdate()) AS LOW,
Getdate() AS HIGH,
'last15minEventCount' AS Label
UNION
SELECT Dateadd(MINUTE, -30, Getdate()) AS LOW,
Dateadd(MINUTE, -15, Getdate()) AS HIGH,
'from15to30EventCount' as Label
--Additional Conditions
)
SELECT Count(*),
B.Label
FROM SystemEvents R
INNER JOIN Boundary B
ON R.TimeStamp BETWEEN LOW AND HIGH
GROUP BY B.Label
这样你就可以将输出变成行,你需要转换成列,我觉得这应该是直截了当的。
这应该很快,因为查询现在是 SARGable,您需要在 TimpeStamp 列上放置非聚集索引。
尝试这样的事情:
SELECT COUNT(1),
SUM(case when r.Timestamp > DATEADD(MINUTE, -15, GETDATE()) AND r.Timestamp <= GETDATE()
then 1
else 0
end) as last15minEventCount,
sum(case when r.Timestamp > DATEADD(MINUTE, -30, GETDATE())AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE())
then 1
else 0
end) as from15to30EventCount,
sum(case when r.Timestamp > DATEADD(MINUTE, -60, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE())
then 1
else 0
end) as from30to60EventCount,
sum(case when r.Timestamp <= DATEADD(MINUTE, -60, GETDATE()
then 1
else 0
end) as olderThan60minEventCount,
FROM dbo.SystemEvents r
为避免每个间隔进行一次扫描,您可以使用 SUM(CASE ... END)
。但是要使查询真正快速,您应该维护一个 table 和总计数并在 TimeStamp
列上创建一个索引:
CREATE TABLE SystemEvents
(
[TimeStamp] datetime
)
GO
CREATE INDEX IX_SystemEvents_TimeStamp
ON dbo.SystemEvents(TimeStamp)
CREATE TABLE SystemEventsTotalCount
(
TotalCount int NOT NULL
)
GO
INSERT INTO SystemEventsTotalCount VALUES ((SELECT COUNT(*) FROM SystemEvents))
GO
CREATE TRIGGER SystemEvents_TotalCount
ON SystemEvents
FOR INSERT, DELETE
AS
DECLARE @InsertedRows int = (SELECT COUNT(*) FROM inserted);
DECLARE @DeletedRows int = (SELECT COUNT(*) FROM deleted);
UPDATE SystemEventsTotalCount
SET TotalCount = TotalCount + @InsertedRows - @DeletedRows
GO
GO
CREATE VIEW EventCounts
AS
WITH b AS
(
SELECT
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -15, GETDATE()) AND r.Timestamp <= GETDATE() THEN 1 ELSE 0 END) AS Last15MinEventCount,
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -30, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE()) THEN 1 ELSE 0 END) AS From15To30EventCount,
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -60, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE()) THEN 1 ELSE 0 END) AS From30to60EventCount,
(SELECT TotalCount FROM dbo.SystemEventsTotalCount) AS TotalEventCount
FROM
dbo.SystemEvents r
WHERE
r.Timestamp > DATEADD(MINUTE, -60, GETDATE())
)
SELECT
b.Last15MinEventCount, b.From15To30EventCount, b.From30to60EventCount,
(b.TotalEventCount - b.Last15MinEventCount - b.From15To30EventCount - b.From30to60EventCount) AS OlderThan60EventCount
FROM b
GO
SELECT * FROM EventCounts
我有一个很大的(几百万行)table 系统事件。我必须获取最近的事件计数,但我不知道如何以正确的方式进行。
我创建了这个视图:
CREATE VIEW [dbo].[EventCounts]
AS
SELECT
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -15, GETDATE())
AND r.Timestamp <= GETDATE()) AS last15minEventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -30, GETDATE())
AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE())) AS from15to30EventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp > DATEADD(MINUTE, -60, GETDATE())
AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE())) AS from30to60EventCount,
(SELECT COUNT(1)
FROM dbo.SystemEvents r
WHERE r.Timestamp <= DATEADD(MINUTE, -60, GETDATE())) AS olderThan60minEventCount
这个观点returns:
- < 15 分钟事件计数;
- 15 - 30 分钟的事件计数;
- 30 - 60 分钟的事件计数;
- 超过 60 分钟的事件计数。
现在我的代码在整个 table 上运行了 4 次,我想优化它。我不能使用预聚合,因为这取决于执行时间。由于同样的原因,我无法使用索引视图。
我想我可以在 Timestamp 列上添加非聚集索引,但代码仍然需要读取整个 table 得到这个计数,对吗?
你能建议如何优化我的查询吗?
P.S。 last15minEventCount、from15to30EventCount、from30to60EventCount - 所有事件的很小一部分。
一个建议,你可以改变方法,像下面这样写你的查询。
CREATE VIEW [dbo].[EventCounts]
AS
WITH Boundary AS (
SELECT Dateadd(MINUTE, -15, Getdate()) AS LOW,
Getdate() AS HIGH,
'last15minEventCount' AS Label
UNION
SELECT Dateadd(MINUTE, -30, Getdate()) AS LOW,
Dateadd(MINUTE, -15, Getdate()) AS HIGH,
'from15to30EventCount' as Label
--Additional Conditions
)
SELECT Count(*),
B.Label
FROM SystemEvents R
INNER JOIN Boundary B
ON R.TimeStamp BETWEEN LOW AND HIGH
GROUP BY B.Label
这样你就可以将输出变成行,你需要转换成列,我觉得这应该是直截了当的。
这应该很快,因为查询现在是 SARGable,您需要在 TimpeStamp 列上放置非聚集索引。
尝试这样的事情:
SELECT COUNT(1),
SUM(case when r.Timestamp > DATEADD(MINUTE, -15, GETDATE()) AND r.Timestamp <= GETDATE()
then 1
else 0
end) as last15minEventCount,
sum(case when r.Timestamp > DATEADD(MINUTE, -30, GETDATE())AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE())
then 1
else 0
end) as from15to30EventCount,
sum(case when r.Timestamp > DATEADD(MINUTE, -60, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE())
then 1
else 0
end) as from30to60EventCount,
sum(case when r.Timestamp <= DATEADD(MINUTE, -60, GETDATE()
then 1
else 0
end) as olderThan60minEventCount,
FROM dbo.SystemEvents r
为避免每个间隔进行一次扫描,您可以使用 SUM(CASE ... END)
。但是要使查询真正快速,您应该维护一个 table 和总计数并在 TimeStamp
列上创建一个索引:
CREATE TABLE SystemEvents
(
[TimeStamp] datetime
)
GO
CREATE INDEX IX_SystemEvents_TimeStamp
ON dbo.SystemEvents(TimeStamp)
CREATE TABLE SystemEventsTotalCount
(
TotalCount int NOT NULL
)
GO
INSERT INTO SystemEventsTotalCount VALUES ((SELECT COUNT(*) FROM SystemEvents))
GO
CREATE TRIGGER SystemEvents_TotalCount
ON SystemEvents
FOR INSERT, DELETE
AS
DECLARE @InsertedRows int = (SELECT COUNT(*) FROM inserted);
DECLARE @DeletedRows int = (SELECT COUNT(*) FROM deleted);
UPDATE SystemEventsTotalCount
SET TotalCount = TotalCount + @InsertedRows - @DeletedRows
GO
GO
CREATE VIEW EventCounts
AS
WITH b AS
(
SELECT
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -15, GETDATE()) AND r.Timestamp <= GETDATE() THEN 1 ELSE 0 END) AS Last15MinEventCount,
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -30, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -15, GETDATE()) THEN 1 ELSE 0 END) AS From15To30EventCount,
SUM(CASE WHEN r.Timestamp > DATEADD(MINUTE, -60, GETDATE()) AND r.Timestamp <= DATEADD(MINUTE, -30, GETDATE()) THEN 1 ELSE 0 END) AS From30to60EventCount,
(SELECT TotalCount FROM dbo.SystemEventsTotalCount) AS TotalEventCount
FROM
dbo.SystemEvents r
WHERE
r.Timestamp > DATEADD(MINUTE, -60, GETDATE())
)
SELECT
b.Last15MinEventCount, b.From15To30EventCount, b.From30to60EventCount,
(b.TotalEventCount - b.Last15MinEventCount - b.From15To30EventCount - b.From30to60EventCount) AS OlderThan60EventCount
FROM b
GO
SELECT * FROM EventCounts