使用局部变量时估计行和实际行有很大差异
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? 或查看官方文档)
这是我在 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? 或查看官方文档)