尽管我希望寻求帮助,但仍在进行扫描

Bes*_*ter 9 index sql-server optimization sql-server-2012

我需要优化一个SELECT语句,但 SQL Server 总是执行索引扫描而不是查找。这是查询,当然,在存储过程中:

CREATE PROCEDURE dbo.something
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL    
AS

    SELECT [IdNumber], [Code], [Status], [Sex], 
           [FirstName], [LastName], [Profession], 
           [BirthDate], [HireDate], [ActiveDirectoryUser]
    FROM Employee
    WHERE (@Status IS NULL OR [Status] = @Status)
    AND 
    (
      @IsUserGotAnActiveDirectoryUser IS NULL 
      OR 
      (
        @IsUserGotAnActiveDirectoryUser IS NOT NULL AND       
        (
          @IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
        )
        OR
        (
          @IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
        )
      )
    )
Run Code Online (Sandbox Code Playgroud)

这是索引:

CREATE INDEX not_relevent ON dbo.Employee
(
    [Status] DESC,
    [ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...); 
Run Code Online (Sandbox Code Playgroud)

计划:

平面图

为什么 SQL Server 选择扫描?我该如何解决?

列定义:

CREATE PROCEDURE dbo.something
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL    
AS

    SELECT [IdNumber], [Code], [Status], [Sex], 
           [FirstName], [LastName], [Profession], 
           [BirthDate], [HireDate], [ActiveDirectoryUser]
    FROM Employee
    WHERE (@Status IS NULL OR [Status] = @Status)
    AND 
    (
      @IsUserGotAnActiveDirectoryUser IS NULL 
      OR 
      (
        @IsUserGotAnActiveDirectoryUser IS NOT NULL AND       
        (
          @IsUserGotAnActiveDirectoryUser = 1 AND ActiveDirectoryUser <> ''
        )
        OR
        (
          @IsUserGotAnActiveDirectoryUser = 0 AND ActiveDirectoryUser = ''
        )
      )
    )
Run Code Online (Sandbox Code Playgroud)

状态参数可以是:

CREATE INDEX not_relevent ON dbo.Employee
(
    [Status] DESC,
    [ActiveDirectoryUser] ASC
)
INCLUDE (...all the other columns in the table...); 
Run Code Online (Sandbox Code Playgroud)

IsUserGotAnActiveDirectoryUser 可以是:

[Status] int NOT NULL
[ActiveDirectoryUser] VARCHAR(50) NOT NULL
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 11

我不认为扫描是由搜索空字符串引起的(虽然您可以为这种情况添加过滤索引,但它只会帮助查询的非常具体的变体)。您更有可能成为参数嗅探的受害者,并且没有针对您将提供给此查询的所有各种参数(和参数值)组合进行优化的单一计划。

我将其称为“厨房水槽”程序,因为您希望通过一个查询来提供所有内容,包括厨房水槽。

在这里这里有关于我的解决方案的视频以及关于它的博客文章,但基本上,我对此类查询的最佳体验是:

  • 动态构建语句- 这将允许您省略提及未提供参数的列的子句,并确保您将拥有一个针对随值传递的实际参数精确优化的计划。
  • 使用OPTION (RECOMPILE)- 这可以防止特定的参数值强制执行错误类型的计划,当您有数据倾斜、错误的统计信息或当语句的第一次执行使用非典型值时,这将导致与以后不同的计划和更频繁的计划时特别有用处决。
  • 使用服务器选项optimize for ad hoc workloads- 这可以防止仅使用一次的查询变体污染您的计划缓存。

为临时工作负载启用优化:

EXEC sys.sp_configure 'show advanced options', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'optimize for ad hoc workloads', 1;
GO
RECONFIGURE WITH OVERRIDE;
GO
EXEC sys.sp_configure 'show advanced options', 0;
GO
RECONFIGURE WITH OVERRIDE;
Run Code Online (Sandbox Code Playgroud)

更改您的程序:

ALTER PROCEDURE dbo.Whatever
  @Status INT = NULL,
  @IsUserGotAnActiveDirectoryUser BIT = NULL
AS
BEGIN 
  SET NOCOUNT ON;
  DECLARE @sql NVARCHAR(MAX) = N'SELECT [IdNumber], [Code], [Status], 
     [Sex], [FirstName], [LastName], [Profession],
     [BirthDate], [HireDate], [ActiveDirectoryUser]
   FROM dbo.Employee -- please, ALWAYS schema prefix
   WHERE 1 = 1';

   IF @Status IS NOT NULL
     SET @sql += N' AND ([Status]=@Status)'

   IF @IsUserGotAnActiveDirectoryUser = 1
     SET @sql += N' AND ActiveDirectoryUser <> ''''';
   IF @IsUserGotAnActiveDirectoryUser = 0
     SET @sql += N' AND ActiveDirectoryUser = ''''';

   SET @sql += N' OPTION (RECOMPILE);';

   EXEC sys.sp_executesql @sql, N'@Status INT, @Status;
END
GO
Run Code Online (Sandbox Code Playgroud)

一旦您根据可以监控的那组查询获得了工作负载,您就可以分析执行并查看哪些从附加索引或不同索引中获益最多 - 您可以从多个角度来执行此操作,从简单的“哪种组合最常提供参数?” 到“哪些单个查询的运行时间最长?” 我们无法仅根据您的代码回答这些问题,我们只能建议任何索引仅对您尝试支持的所有可能参数组合的一个子集有帮助。例如,如果@Status为 NULL,则不可能针对该非聚集索引进行查找。因此,对于那些用户不关心状态的情况,您将进行扫描,除非您有一个满足其他子句的索引(但鉴于您当前的查询逻辑,这样的索引也没有用- 空字符串或非空字符串都不是完全选择性的)。

在这种情况下,根据可能Status值的集合以及这些值的分布情况,OPTION (RECOMPILE)可能没有必要。但是,如果您确实有一些会产生 100 行的值和一些会产生数十万行的值,您可能希望在那里(即使 CPU 成本,考虑到此查询的复杂性,这应该是微不足道的),以便您可以get 在尽可能多的情况下进行搜索。如果值的范围足够有限,你甚至可以用动态 SQL 做一些棘手的事情,你说“我有这个非常有选择性的值@Status,所以当这个特定的值被传递时,对查询文本做这个轻微的改变,以便这被视为不同的查询并针对该参数值进行了优化。”

  • 我已经多次使用这种方法,这是让优化器按照您认为它应该做的方式做事的绝妙方法。Kim Tripp 在这里谈到了一个类似的解决方案:http://www.sqlskills.com/blogs/kimberly/high-performance-procedures/ 并且有一段她几年前在 PASS 上做的会议的视频,这真的很疯狂详细说明它为什么起作用。这就是说,它对 Bertrand 先生在这里所说的内容确实没有增加任何意义。这是每个人都应该保留在工具带中的工具之一。它确实可以为那些包罗万象的查询节省一些巨大的痛苦。 (3认同)