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