需要帮助来了解调优慢速 SQL 服务器查询

New*_*DBA 3 sql-server sql-server-2012 query-performance performance-tuning

对于我们的一个数据库,如下所示的查询非常慢。

由于安全原因,我无法分享实际的查询或计划,但只是想知道如何编写查询如下

SELECT [Id]
      ,[AboutMe]
      ,[Age]
      ,[CreationDate]
      ,[DisplayName]
      ,[DownVotes]
      ,[EmailHash]
      ,[LastAccessDate]
      ,[Location]
      ,[Reputation]
      ,[UpVotes]
      ,[Views]
      ,[WebsiteUrl]
      ,[AccountId]
  FROM [StackOverflow2010].[dbo].[Users]
  WHERE DisplayName IN (

 SELECT DisplayName from dbo.Users
 WHERE CAST(LastAccessDate AS DATE) = CAST ('20160814' AS DATE)
 AND CreationDate>= DATEADD (DAY, -30,LastAccessDate)
 AND CreationDate<= LastAccessDate)
Run Code Online (Sandbox Code Playgroud)

在 stackoverflow 数据库中,这不会返回任何行,但对于我们现有的 6 TBS 数据库,它真的很慢。

CreationDateLastAccessdate列都是日期时间 (10)

DisplayName是VARCHAR(50)

如果以上可以重写,请建议我如何提高性能

Jos*_*ell 6

据我所知,只需添加索引,您就可以在不重写查询的情况下大大改进事情。

这是我在 SQL Server 2019 上得到的执行计划(请注意,我将日期更改为'20090814'只是为了返回一些结果):

在此处输入图片说明

Table 'Users'. Scan count 18, logical reads 15430

 SQL Server Execution Times:
   CPU time = 297 ms,  elapsed time = 94 ms.
Run Code Online (Sandbox Code Playgroud)

这将扫描整个 Users 表一次以获取满足日期条件的用户,然后再扫描几次以获取具有匹配显示名称的其余用户。

不理想。

查询在我的机器上只需要 94 毫秒,主要是因为所有内容都在 RAM 中,并且查询以并行度 (DOP) 8 运行。

但是,添加这两个索引对情况有很大帮助:

CREATE NONCLUSTERED INDEX IX_LastAccessDate 
ON dbo.Users (LastAccessDate)
INCLUDE (CreationDate, DisplayName);

CREATE NONCLUSTERED INDEX IX_DisplayName
ON dbo.Users (DisplayName);
Run Code Online (Sandbox Code Playgroud)

现在我得到了这个执行计划:

计划资源管理器中执行计划的屏幕截图显示索引搜索而不是扫描

CREATE NONCLUSTERED INDEX IX_LastAccessDate 
ON dbo.Users (LastAccessDate)
INCLUDE (CreationDate, DisplayName);

CREATE NONCLUSTERED INDEX IX_DisplayName
ON dbo.Users (DisplayName);
Run Code Online (Sandbox Code Playgroud)

SQL Server能够生产计划寻求通过在这里使用内部GetRangeThroughConvert函数,以确定可能的范围datetime等效于值CAST(LastAccessDate AS DATE) = CAST ('20160814' AS DATE)。本质上,它在后台重写查询以匹配Mo64 提出的建议

注意:您最好重写以显式使用该范围查询,而不是依赖于隐藏的隐式转换

您可以在 Paul White 的博客上阅读有关此类执行计划的更多详细信息:Dynamic Seeks and Hidden Implicit Conversions

如果您消除键查找,这种情况可能会得到进一步改善 - 通过选择较少的列,或在第一个索引中包含所有必需的列 (on LastAccessDate)。


我意识到这是一个玩具示例,但希望它说明了可以应用于您的实际情况的一般解决方案:

  • 添加一个索引,允许 SQL Server 跳转到子查询中的正确日期
  • 潜在地重写CASTdatetimedate是两个不等式条件。换句话说,替换了这个:
WHERE CAST(LastAccessDate AS DATE) = CAST ('20160814' AS DATE)
Run Code Online (Sandbox Code Playgroud)

有了这个:

WHERE LastAccessDate >= '20160814' AND LastAccessDate < '20160815' 
Run Code Online (Sandbox Code Playgroud)
  • 添加一个索引,允许 SQL Server 跳转到匹配的varchar(50)列值