SQL 在非常大的情况下使用 CTE 与 WHILE 循环的查询性能 table
SQL query performance using CTE vs WHILE loop in very large table
我正在尝试根据以下情况找出性能最佳的查询。
下面的table非常大,超过500万行。每天都会发生一些负载,尽管确切的次数有所不同。每个负载由 load_id(聚簇索引的一部分)标识,load_datetimestamp 是适用于该负载的时间戳。每次加载插入大约 30,000 行。
CREATE TABLE [VeryLargeTable](
[load_id] [int] NOT NULL,
[acct_cd] [varchar](20) NOT NULL,
[acct_num] [varchar](255) NULL,
[prod_id] [varchar](50) NOT NULL,
[domestic_foreign_cd] [varchar](3) NOT NULL,
[vendor_prod_id] [varchar](15) NULL,
[prod_name] [varchar](100) NULL,
[currency_cd] [varchar](3) NULL,
[total_Qty] [int] NULL,
[mkt_price] [float] NULL,
[load_datetimestamp] [datetime] NULL,
CONSTRAINT [pk_VeryLargeTable] PRIMARY KEY CLUSTERED (
[load_id] ASC,
[acct_cd] ASC,
[prod_id] ASC,
[domestic_foreign_cd] ASC )
)
在任何给定的晚上,我想从今天的第一次加载中获取所有行。在 DDL 上面给出的查询必须尽可能高效。显然,您不想在整个 table.
上以 "WHERE datediff(day,load_datetimestamp,getDate())=0" 之类的东西开始
我写了这 2 个查询。有哪一个比另一个更好吗?我知道两者 return 结果相同。你能推荐一个比这两个中的任何一个更好的吗?
查询 1
With
T1 as (select
load_id,
load_datetimestamp
from dbo.VeryLargeTable
group by
load_id,load_datetimestamp),
T2 as (select
load_id,
load_datetimestamp
from T1
where
datediff(day,load_datetimestamp,getDate())=0),
T3 as (select min(load_id) as loadID from T2)
select * from dbo.VeryLargeTable
where load_id = (select loadID from T3)
查询 2
declare @found tinyint;
declare @loadID int;
declare @dateTimeStamp datetime;
-- Get max value of load id
select @loadID = max(load_id) from [dbo].[VeryLargeTable];
-- Keep looping until previous day is found or minimum load_id is reached
set @found = 0;
WHILE (@found=0)
BEGIN
select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;
if (@loadID=0) SET @found=1
else
BEGIN
if (DATEPART(day, @dateTimeStamp) = DATEPART(day, GetDate())) SET @loadID = @loadID - 1;
else SET @found=1;
END
END
SELECT * from [dbo].[VeryLargeTable] where load_id=(@loadID + 1);
你需要检查你的执行计划,但如果我是你,我会使用临时 table 而不是使用 3 table 表达式并将其与我原来的 table .
另外,正如您之前提到的,最好不要在 WHERE
子句中使用函数。相反,您可以使用 date BETWEEN start AND end
这里不需要 3 个 CTE。您可以通过简单地 selecting 满足过滤条件的 Load ID 和 Load DateTimeStamp 的所有不同组合来替换前 2 个 CTE。然后,您可以通过将该检查直接移动到最终 select 中的子查询来摆脱第三个 CTE。结果查询将如下所示:
;With
T2 as
(select distinct
load_id,
load_datetimestamp
from dbo.VeryLargeTable
where datediff(day,load_datetimestamp,getDate())=0)
select * from dbo.VeryLargeTable
where load_id = (select min(load_ID) from T2)
1) 正如评论中提到的,不要使用像 datediff(day,load_datetimestamp,getDate())=0)
这样的表达式。此表达式不能在列 load_datetimestamp
.
上使用索引
使用load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
.
2) 在列 load_datetimestamp
.
上创建索引
3) 找到今天加载的第一行并从中取出load_id
:
SELECT TOP(1) load_id
FROM dbo.VeryLargeTable
WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
ORDER BY load_datetimestamp
此查询应该是对 load_datetimestamp
上的索引的即时查找。检查执行计划。
4) 在 load_id
列上创建索引。
5) 使用找到的 load_id
到 return 负载的所有行:
WITH
CTE_FirstLoad
AS
(
SELECT TOP(1) load_id
FROM dbo.VeryLargeTable
WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
ORDER BY load_datetimestamp
)
SELECT *
FROM dbo.VeryLargeTable
WHERE load_id IN (SELECT load_id FROM CTE_FirstLoad)
;
这应该使用 load_id
上的索引。
我编辑了答案并在最后一个 WHERE
中放置了 IN
而不是 =
。使用 IN
,即使今天根本没有负载,查询也会工作。 =
很可能会出现错误。
编辑
现在我们知道您无法在此 table 上创建新索引。而且我们知道每天加载的次数很少(5次左右)。
有了这个限制,使用显式循环的第二个查询很可能是最快的。无论如何,您必须在真实系统上尝试所有建议并衡量它们的性能。
我们可以假设每个负载在同一天开始和结束吗?换句话说,对于每个 load_id
load_datetimestamp
的日期是否相同?
然后,您可以尝试对循环进行一些调整。它们可能不会或根本不会改变性能,但你必须衡量。
而不是:
select @loadID = max(load_id) from [dbo].[VeryLargeTable];
尝试:
SET @loadID =
(select TOP(1) load_id from [dbo].[VeryLargeTable] ORDER BY load_id DESC);
在循环中以相同的方式(我怀疑当前行是否有效,因为相同的行有很多行 load_id
)而不是:
select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;
尝试:
SET @dateTimeStamp =
(select TOP(1) load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID);
如果允许您创建另一个 table
我假设负载 ID 随时间增加,即 ID=N
的负载比 ID=N+1
的负载少 load_datetimestamp
。
创建一个单独的 table Loads
列:load_id
主唯一聚集索引位于 load_id
。
此 table 将包含每一天最后 load_id
的一行。它会比主要的 table.
小得多
这个table的目的是快速找到"first load_id for today"而不是扫描大的table。
每天晚上当您 运行 您的报告这么小 Loads
table 将包含前几天的负载行,但不是今天的负载行。
你能说,当你 运行 你晚上的报告已经完成了当天的所有加载吗?如果是:
从小table:
获取前几天的最后负载
SET @PrevLoadID =
(SELECT TOP(1) load_id FROM Loads ORDER BY load_id DESC)
从大 table:
获取今天的第一个负载
SET @TodayFirstLoadID =
(SELECT TOP(1) load_id
FROM VeryLargeTable
WHERE VeryLargeTable.load_id > @PrevLoadID
ORDER BY load_id ASC)
从大 table:
中获取今天的最后负载
SET @TodayLastLoadID =
(SELECT TOP(1) load_id
FROM VeryLargeTable
ORDER BY load_id DESC)
INSERT
把@TodayLastLoadID
改成Loads
.
使用@TodayFirstLoadID
来运行你的主要报告:
SELECT *
FROM VeryLargeTable
WHERE load_id = @TodayFirstLoadID
我正在尝试根据以下情况找出性能最佳的查询。
下面的table非常大,超过500万行。每天都会发生一些负载,尽管确切的次数有所不同。每个负载由 load_id(聚簇索引的一部分)标识,load_datetimestamp 是适用于该负载的时间戳。每次加载插入大约 30,000 行。
CREATE TABLE [VeryLargeTable](
[load_id] [int] NOT NULL,
[acct_cd] [varchar](20) NOT NULL,
[acct_num] [varchar](255) NULL,
[prod_id] [varchar](50) NOT NULL,
[domestic_foreign_cd] [varchar](3) NOT NULL,
[vendor_prod_id] [varchar](15) NULL,
[prod_name] [varchar](100) NULL,
[currency_cd] [varchar](3) NULL,
[total_Qty] [int] NULL,
[mkt_price] [float] NULL,
[load_datetimestamp] [datetime] NULL,
CONSTRAINT [pk_VeryLargeTable] PRIMARY KEY CLUSTERED (
[load_id] ASC,
[acct_cd] ASC,
[prod_id] ASC,
[domestic_foreign_cd] ASC )
)
在任何给定的晚上,我想从今天的第一次加载中获取所有行。在 DDL 上面给出的查询必须尽可能高效。显然,您不想在整个 table.
上以 "WHERE datediff(day,load_datetimestamp,getDate())=0" 之类的东西开始我写了这 2 个查询。有哪一个比另一个更好吗?我知道两者 return 结果相同。你能推荐一个比这两个中的任何一个更好的吗?
查询 1
With
T1 as (select
load_id,
load_datetimestamp
from dbo.VeryLargeTable
group by
load_id,load_datetimestamp),
T2 as (select
load_id,
load_datetimestamp
from T1
where
datediff(day,load_datetimestamp,getDate())=0),
T3 as (select min(load_id) as loadID from T2)
select * from dbo.VeryLargeTable
where load_id = (select loadID from T3)
查询 2
declare @found tinyint;
declare @loadID int;
declare @dateTimeStamp datetime;
-- Get max value of load id
select @loadID = max(load_id) from [dbo].[VeryLargeTable];
-- Keep looping until previous day is found or minimum load_id is reached
set @found = 0;
WHILE (@found=0)
BEGIN
select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;
if (@loadID=0) SET @found=1
else
BEGIN
if (DATEPART(day, @dateTimeStamp) = DATEPART(day, GetDate())) SET @loadID = @loadID - 1;
else SET @found=1;
END
END
SELECT * from [dbo].[VeryLargeTable] where load_id=(@loadID + 1);
你需要检查你的执行计划,但如果我是你,我会使用临时 table 而不是使用 3 table 表达式并将其与我原来的 table .
另外,正如您之前提到的,最好不要在 WHERE
子句中使用函数。相反,您可以使用 date BETWEEN start AND end
这里不需要 3 个 CTE。您可以通过简单地 selecting 满足过滤条件的 Load ID 和 Load DateTimeStamp 的所有不同组合来替换前 2 个 CTE。然后,您可以通过将该检查直接移动到最终 select 中的子查询来摆脱第三个 CTE。结果查询将如下所示:
;With
T2 as
(select distinct
load_id,
load_datetimestamp
from dbo.VeryLargeTable
where datediff(day,load_datetimestamp,getDate())=0)
select * from dbo.VeryLargeTable
where load_id = (select min(load_ID) from T2)
1) 正如评论中提到的,不要使用像 datediff(day,load_datetimestamp,getDate())=0)
这样的表达式。此表达式不能在列 load_datetimestamp
.
使用load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
.
2) 在列 load_datetimestamp
.
3) 找到今天加载的第一行并从中取出load_id
:
SELECT TOP(1) load_id
FROM dbo.VeryLargeTable
WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
ORDER BY load_datetimestamp
此查询应该是对 load_datetimestamp
上的索引的即时查找。检查执行计划。
4) 在 load_id
列上创建索引。
5) 使用找到的 load_id
到 return 负载的所有行:
WITH
CTE_FirstLoad
AS
(
SELECT TOP(1) load_id
FROM dbo.VeryLargeTable
WHERE load_datetimestamp >= CAST(CAST(GETDATE() AS date) AS datetime)
ORDER BY load_datetimestamp
)
SELECT *
FROM dbo.VeryLargeTable
WHERE load_id IN (SELECT load_id FROM CTE_FirstLoad)
;
这应该使用 load_id
上的索引。
我编辑了答案并在最后一个 WHERE
中放置了 IN
而不是 =
。使用 IN
,即使今天根本没有负载,查询也会工作。 =
很可能会出现错误。
编辑
现在我们知道您无法在此 table 上创建新索引。而且我们知道每天加载的次数很少(5次左右)。
有了这个限制,使用显式循环的第二个查询很可能是最快的。无论如何,您必须在真实系统上尝试所有建议并衡量它们的性能。
我们可以假设每个负载在同一天开始和结束吗?换句话说,对于每个 load_id
load_datetimestamp
的日期是否相同?
然后,您可以尝试对循环进行一些调整。它们可能不会或根本不会改变性能,但你必须衡量。
而不是:
select @loadID = max(load_id) from [dbo].[VeryLargeTable];
尝试:
SET @loadID =
(select TOP(1) load_id from [dbo].[VeryLargeTable] ORDER BY load_id DESC);
在循环中以相同的方式(我怀疑当前行是否有效,因为相同的行有很多行 load_id
)而不是:
select @dateTimeStamp = load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID;
尝试:
SET @dateTimeStamp =
(select TOP(1) load_datetimestamp from [dbo].[VeryLargeTable] where load_id=@loadID);
如果允许您创建另一个 table
我假设负载 ID 随时间增加,即 ID=N
的负载比 ID=N+1
的负载少 load_datetimestamp
。
创建一个单独的 table Loads
列:load_id
主唯一聚集索引位于 load_id
。
此 table 将包含每一天最后 load_id
的一行。它会比主要的 table.
这个table的目的是快速找到"first load_id for today"而不是扫描大的table。
每天晚上当您 运行 您的报告这么小 Loads
table 将包含前几天的负载行,但不是今天的负载行。
你能说,当你 运行 你晚上的报告已经完成了当天的所有加载吗?如果是:
从小table:
获取前几天的最后负载SET @PrevLoadID =
(SELECT TOP(1) load_id FROM Loads ORDER BY load_id DESC)
从大 table:
获取今天的第一个负载SET @TodayFirstLoadID =
(SELECT TOP(1) load_id
FROM VeryLargeTable
WHERE VeryLargeTable.load_id > @PrevLoadID
ORDER BY load_id ASC)
从大 table:
中获取今天的最后负载SET @TodayLastLoadID =
(SELECT TOP(1) load_id
FROM VeryLargeTable
ORDER BY load_id DESC)
INSERT
把@TodayLastLoadID
改成Loads
.
使用@TodayFirstLoadID
来运行你的主要报告:
SELECT *
FROM VeryLargeTable
WHERE load_id = @TodayFirstLoadID