使用局部变量时估计行和实际行有很大差异

Big difference in Estimated and Actual rows when using a local variable

这是我在 Whosebug 上的第一个 post,所以我希望我正确地遵循了所有协议!

我正在努力处理一个存储过程,在该存储过程中我创建了一个 table 变量并使用内连接用插入语句填充此 table。插入本身很简单,但它变得复杂,因为内部连接是在局部变量上完成的。由于优化器没有这个变量的统计信息,我估计的行数被弄乱了。

导致问题的具体代码:

declare @minorderid int
select @minorderid = MIN(lo.order_id)
from [order] lo with(nolock)
where lo.order_datetime >= @datefrom

insert into @OrderTableLog_initial
(order_id, order_log_id, order_id, order_datetime, account_id, domain_id)

    select ot.order_id, lol.order_log_id, ot.order_id, ot.order_datetime, ot.account_id, ot.domain_id
    from [order] ot with(nolock)

inner join order_log lol with(nolock)
on ot.order_id = lol.order_id
and ot.order_datetime >= @datefrom

where (ot.domain_id in (1,2,4) and lol.order_log_id not in ( select order_log_id 
                                        from dbo.order_log_detail lld with(nolock)
                                        where order_id >= @minorderid
                                    )
or
(ot.domain_id = 3 and ot.order_id not IN (select order_id 
                                        from dbo.order_log_detail_spa llds with(nolock)
                                        where order_id >= @minorderid
                                        )
))

order by lol.order_id, lol.order_log_id

@datefrom局部变量也在存储过程的前面声明:

    declare @datefrom datetime
    if datepart(hour,GETDATE()) between 4 and 9
    begin
        set @datefrom = '2011-01-01'
    end
    else
    begin
        set @datefrom = DATEADD(DAY,-2,GETDATE())   
    end

我还使用临时 table 代替 table 变量对此进行了测试,但没有任何变化。但是,当我用固定日期戳替换局部变量 >= @datefrom 时,我的估计值和实际值几乎相同。

ot.order_datetime >= @datefrom = SQL Sentry Plan Explorer

ot.order_datetime >= '2017-05-03 18:00:00.000' = SQL Sentry Plan Explorer

我了解到有一种方法可以通过将此代码转换为动态 sp 来解决此问题,但我不确定该怎么做。如果有人可以就如何执行此操作向我提出建议,我将不胜感激。也许我必须使用完全不同的方法?如果我忘了说什么,请原谅我,这是我的第一个 post.

编辑:

MSSQL 版本 = 11.0.5636

我也用跟踪标志 2453 进行了测试,但没有成功

此致, 彼得

确实,您遇到的行为是因为变量。 SQL 服务器不会为每个可能的输入存储执行计划,因此对于某些查询,执行计划可能是最优的,也可能不是最优的。

要回答您的明确问题:您必须创建一个 varchar 变量并将查询构建为字符串,然后执行它。

实际代码前的一些注释:

  • 这可能容易受到 SQL 注入(一般而言)
  • SQL 服务器将单独存储计划,这意味着它们将使用更多内存并可能从缓存中剔除其他计划

使用假想的设置,这就是你想要做的:

DECLARE @inputDate DATETIME2 = '2017-01-01 12:21:54';

DELCARE @dynamiSQL NVARCHAR(MAX) = CONCAT('SELECT col1, col2 FROM MyTable WHERE myDateColumn = ''', FORMAT(@inputDate, 'yyyy-MM-dd HH:mm:ss'), ''';');

INSERT INTO @myTableVar (col1, col2)
EXEC sp_executesql @stmt = @dynamicSQL;

补充说明:

  • 您可以尝试使用 EXISTS 和 NOT EXISTS 代替 IN 和 NOT IN。
  • 您可以尝试使用临时 table (#myTempTable) 而不是局部变量并在其上放置一些索引。物理临时 tables 可以在处理大量数据时表现更好,您可以在其上放置索引。 (有关更多信息,您可以访问此处:What's the difference between a temp table and table variable in SQL Server? 或查看官方文档)