Tsql 查询速度因 Or 内 where 子句而变慢,导致索引扫描而不是搜索

Use*_*ith 0 performance index sql-server t-sql query-performance

我一直在努力使这个查询更有效地工作。

我发现 where 子句中 Or 的数量是这个查询中最大的问题。此查询位于存储过程中。

我到了我能想到的唯一选择的地步。:

  • 为所有不同的入站参数可能性创建 16 种不同的查询。
  • 创建动态 sql 查询,但我认为这不会更快
  • 恢复到字符串 sql,但我不喜欢这样做,因为它们的执行速度不如存储过程。

我相信其他人以前也遇到过这个问题。查询性能在大约一秒或更短的时间内开始并不可怕,但在某些情况下,它被多次命中,导致长达 5 或 6 秒的延迟。

查询如下。:

DECLARE @PERSON_ID AS INT
DECLARE @ITEM_ID AS INT
DECLARE @ITEM_VERSION AS INT
DECLARE @ITEM_SUB_NAME AS VARCHAR(250)
DECLARE @ITEM_SUB_SUB_NAME AS VARCHAR(250)

--DEFAULTS
SET @PERSON_ID = 0
SET @ITEM_ID = 0
SET @ITEM_VERSION = 1
SET @ITEM_SUB_NAME = NULL
SET @ITEM_SUB_SUB_NAME = NULL

    SELECT ID, PERSON_ID, 
           ISNULL(ITEM_VERSION, 1) AS ITEM_VERSION,
           ISNULL(ITEM_SUB_NAME, '') AS 'ITEM_SUB_NAME',
           ISNULL(ITEM_SUB_SUB_NAME, '') AS 'ITEM_SUB_SUB_NAME',
           ISNULL(ITEM_DATE, '1/1/1900') AS 'ITEM_DATE',
    FROM PERSON_TBL s WITH (NOLOCK)     
    WHERE    ( PERSON_ID = @PERSON_ID OR @PERSON_ID = 0 )
    AND ( ITEM_VERSION = @ITEM_VERSION OR ( @ITEM_VERSION = 1 AND ITEM_VERSION IS NULL ))
    AND ( EMPLOYEE_ID = @EMPLOYEE_ID OR @EMPLOYEE_ID = 0 )
    AND ( ITEM_SUB_NAME = @ITEM_SUB_NAME OR @ITEM_SUB_NAME IS NULL )
    AND ( ITEM_SUB_SUB_NAME = @ITEM_SUB_SUB_NAME OR @ITEM_SUB_SUB_NAME IS NULL )
    ORDER BY PERSON_ID, ITEM_SUB_NAME
Run Code Online (Sandbox Code Playgroud)

Aar*_*and 9

这就是我所说的“厨房水槽”存储过程——您需要一个过程来处理用户可能输入的所有可能的搜索条件组合。几乎不可能让 SQL Server 派生出一个对所有这些组合都是最佳和高效的单一执行计划——我不在乎你可能认为什么样的技巧ISNULL可以COALESCEOR不能。

我通常尝试的解决方案是:

  1. 添加OPTION (RECOMPILE)到查询中。是的,您每次都需要支付编译成本,但是您会根据提供的参数及其值获得正确的计划。

  2. 使用动态 SQL。现在,您将能够根据传递的不同参数缓存多个不同的计划。将此与服务器级设置optimize for ad hoc workloads(来自 Kimberly Tripp此处此处的更多信息)相结合,以便仅完全缓存多次使用的计划版本。例子:

    DECLARE
      @PERSON_ID         INT = 0,
      @ITEM_ID           INT = 0,
      @ITEM_VERSION      INT = 1,
      @ITEM_SUB_NAME     VARCHAR(250),
      @ITEM_SUB_SUB_NAME VARCHAR(250);
    
    DECLARE @sql NVARCHAR(MAX) = N'',
    
    SET @sql = N'SELECT ID, PERSON_ID, 
       ISNULL(ITEM_VERSION, 1) AS ITEM_VERSION,
       ISNULL(ITEM_SUB_NAME, '''') AS ITEM_SUB_NAME,
       ISNULL(ITEM_SUB_SUB_NAME, '''') AS ITEM_SUB_SUB_NAME,
       ISNULL(ITEM_DATE, ''19000101'') AS ITEM_DATE
    FROM dbo.PERSON_TBL AS s WITH (NOLOCK)     
    WHERE ITEM_VERSION ' 
    + CASE WHEN @ITEM_VERSION <> 1 THEN 
       N' = @ITEM_VERSION' ELSE N' IS NULL' END
    + CASE WHEN @PERSON_ID <> 0 THEN 
       N' AND PERSON_ID = @PERSON_ID' ELSE N'' END
    + CASE WHEN @EMPLOYEE_ID <> 0 THEN
       N' AND EMPLOYEE_ID = @EMPLOYEE_ID' ELSE N'' END
    + CASE WHEN @ITEM_SUB_NAME IS NOT NULL THEN
       N' AND ITEM_SUB_NAME = @ITEM_SUB_NAME' ELSE N'' END
    + CASE WHEN @ITEM_SUB_SUB_NAME IS NOT NULL THEN
       N' AND ITEM_SUB_SUB_NAME = @ITEM_SUB_SUB_NAME' ELSE N'' END
    ORDER BY PERSON_ID, ITEM_SUB_NAME;';
    
    EXEC sys.sp_executesql @sql,
      N'@PERSON_ID INT, @ITEM_ID INT, @ITEM_VERSION INT,
        @ITEM_SUB_NAME VARCHAR(250), @ITEM_SUB_SUB_NAME VARCHAR(250)',
      @PERSON_ID, @ITEM_ID, @ITEM_VERSION, 
      @ITEM_SUB_NAME, @ITEM_SUB_SUB_NAME;
    
    Run Code Online (Sandbox Code Playgroud)

如果您发现参数嗅探仍然是一个问题,即使您对提供的每种参数组合都有单独的计划(由于基于实际值的基数差异很大),您也可以OPTION (RECOMPILE)在动态 SQL 中添加- 您再次支付编译成本,但当你有一个更简单的 where 子句时,它应该会更好。

关于此模式的一些资源:

Paul White 也有一篇很棒的文章值得一读:

作为旁白: