【问题标题】:SQL Server: IF EXISTS massively slowing down a querySQL Server:如果存在会大大减慢查询速度
【发布时间】:2015-09-10 23:56:48
【问题描述】:

(正在使用 SQL Server 2012)

我找到了一些关于查询优化的主题,并将 EXISTS 与 COUNT 进行比较,但我找不到这个确切的问题。

我有一个看起来像这样的查询:

select * from
tblAccount as acc
join tblUser as user on acc.AccountId = user.AccountId
join tblAddress as addr on acc.AccountId = addr.AccountId
... **a few more joins**
where acc.AccountId in (
    select * accountid from
    (select accountid, count(*) from tblUser
    where flag = 1
    group by accountId) as tbl where c != 1

这个查询在瞬间运行(虽然 db 很大,大约 70Gb)。

当我将查询包装在 EXISTS 中时:

if exists
(
  **Exact same query as above**
)
begin
RAISERROR('Account found without exactly one flagged user.', 16, 1);
end
else
begin
  print 'test passed.'
end

查询突然需要大约 5-6 秒才能完成。我已经尝试指定 IF EXISTS (SELECT TOP 1 FROM... 并且还尝试了 NOT EXISTS (甚至更慢)。但都无法加快速度。

如果正常的选择查询基本上立即完成,那么有谁知道为什么将它包装在 EXISTS 中会导致这么多额外的计算?和/或任何人有任何解决此问题的想法(如果原始查询完全找到任何记录,我只是试图抛出错误)。

谢谢!

【问题讨论】:

  • EXISTS 中包裹的查询是否完全相同?选择前 1 不会做任何事情,因为存在扫描表,如果找到记录,则返回 true 并且不打扰其他计算 afaik。但首先我想我会从执行计划开始。
  • 是的,查询完全相同。已编辑原始问题以使其更清楚。不确定执行计划是什么意思?
  • 可能是普通查询被缓存,而不是每次都实际查询数据,而不是exist()
  • 执行计划显示在查询执行期间究竟做了什么。您可以在链接中启用它,然后在此处截屏并上传两个计划,这样我们就可以看到发生了什么。 i.imgur.com/oiLRdLb.png
  • @user1221684 - SQL Server 不缓存实际查询结果。刚刚编译了计划缓存中的执行计划和缓冲池中的底层数据/索引页。

标签: sql sql-server sql-server-2012 query-optimization exists


【解决方案1】:

您是否尝试过使用 TOP 1 运行原始查询?很可能它会一样慢。

有时,当优化器认为某事很有可能并且会毫不费力地返回大量数据(即几乎所有记录都将被返回)时,它主要选择循环连接,因为它只需要获取第一个one and a loop join 适用于只获取几条记录。如果事实证明并非如此,则需要永远和一天的时间才能得到结果。

在你的情况下,这听起来很罕见,所以这个选择很伤人。请尝试执行SELECT @count = COUNT(*) FROM ... 之类的操作,然后检查该计数是否非零。

【讨论】:

  • 是的,同意。想象一下这将是一个row goal 问题。
  • 感谢您的回答!不能说我完全理解,但我想我明白了要点。解决方法非常有效。
  • 我在问了我的问题后偶然发现了这个问题:stackoverflow.com/questions/31032353/… 这与我的性能从 70 毫秒下降到 4 分钟以上的原因相同吗?
  • 鉴于您所看到的行为,是的,我会说这是一回事。我会尝试再次解释你的问题。
【解决方案2】:

我也解决了这个问题。

当我自己运行它时,查询是 10 毫秒,但是一旦我把它放在 If Exists 中,它就变成了 4 分钟。无论我尝试什么,它都不会回到 10 毫秒。该问题在 4 台不同的服务器上重现,但在 2 台服务器上未重现。服务器都具有相同的数据库备份和相同的 mssql 2012 补丁级别。服务器位于不同的操作系统和不同的硬件设置上。

我试过了

  • 调整最大内存授予 - 无影响
  • 更改并行度的阈值 - 无影响
  • 重写查询以使其更简单 - 无影响
  • 使用前 1 - 没有影响
  • 在更改之间清除缓存 - 无影响
  • 将查询分解为一些我可以的索引视图(不能对使用外连接的部分执行此操作)- 无影响
  • 应用了推荐的缺失索引 - 将时间从 4 分钟缩短到 3 分钟,但仍然没有达到我预期的 10 毫秒。
  • 将外连接更改为 where not in(子查询)- 无影响
  • 运行 sp_updateStats - 无影响

对我有用的唯一解决方案是将结果放在临时表中,然后针对该临时表执行 if exists。

SELECT top 1 1 AS junk INTO #me FROM yourCraxyQuery这里 如果存在(选择 1 从我 ) 选择获取日期()

希望对你有帮助

【讨论】:

  • 操作示例,用于adjusting the max memory grantchanging the threshold for parallelismcleared the cache between changesbreak the query into some indexed viewsapplied recommended missing indexrun sp_updateStats ?
  • 我有一个类似的情况,当包裹在 IF EXISTS 中时,查询从大约 5 秒到 12 分钟。我选择了一个临时表,然后在临时表上执行了 IF EXISTS,它又回到了 5 秒。
【解决方案3】:

试试:

if exists
(
  select 1 from... etc
)

【讨论】:

  • 由于 IF EXISTS 真的只检查 存在 而实际上并不返回任何数据,这是毫无意义的,实际上与 IF EXISTS (SELECT * ....) 完全相同
  • “*将被扩展为一些可能很大的列列表,然后确定EXISTS的语义不需要这些列中的任何一个,因此基本上可以将它们全部删除。 “SELECT 1”将避免在查询编译期间检查该表的任何不需要的元数据。”
  • @marc_s - 它会检查列元数据,但1 也会发生。
  • @MartinSmith: 好的 - 但重点是 IF EXISTS (SELECT * ...)IF EXISTS (SELECT 1 ....) 相同 并且至少在 SQL Server 中产生完全相同的执行计划 - 所以使用SELECT 1 而不是SELECT * 没有任何收获 - 对吗?
  • @marc_s - 是的。 1* 以及扩展列元数据的效果在我的回答中查看stackoverflow.com/a/6140367/73226SELECT 1 WHERE EXISTS (SELECT 1 FROM T); 仍然可能会因T 上缺少列权限而意外失败,这表明权限已被检查。 1* 都会出现相对于列数的较慢编译。
【解决方案4】:

对我有用的是将查询结果设置为变量,然后比较变量,不要问我为什么,它只是为我醒来。无法解释

【讨论】:

  • 编辑您的答案以包含所描述的 SQL 脚本会很有帮助。您可以根据需要编辑或重命名敏感元数据值。
【解决方案5】:

你有,什么,三个嵌套子查询?子查询总是很慢。您可以将其中至少一个转换为联接吗?如:

select acc.AccountId from tblAccount as acc
    join tblUser as user on acc.AccountId = user.AccountId
    join tblAddress as addr on acc.AccountId = addr.AccountId
    join (select accountid, count(*) as c from tblUser
          where flag = 1
          group by accountId) as tbl ON tbl.accountid = user.accountid
    where tbl.c != 1

【讨论】:

    【解决方案6】:

    尝试 SELECT 1 而不是 top 1 *。您实际上不需要返回数据,您只是在检查记录是否存在。

    如果是存储过程,您也可以尝试清除查询缓存,如下所示:

    获取计划句柄:

    SELECT qs.plan_handle
    FROM sys.procedures AS p WITH (NOLOCK)
    INNER JOIN sys.dm_exec_procedure_stats AS qs WITH (NOLOCK)
    ON p.[object_id] = qs.[object_id]
    WHERE qs.database_id = DB_ID()
    and p.name like '%SprocName%'
    ORDER BY qs.execution_count DESC OPTION (RECOMPILE);
    

    然后通过将句柄放入此调用来清除缓存:

    DBCC FREEPROCCACHE (0x05000F00C616D37C40E15E64010000000000000000000000);
    

    【讨论】:

    • 正如评论其他答案之一,将 * 更改为 1 不会有任何区别。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-11-22
    • 2011-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多