索引不会加快执行速度,并且在某些情况下会减慢查询速度。为什么会这样?

Spe*_*ine 36 index sql-server

我正在尝试使用索引来加快速度,但是在连接的情况下,索引并没有改善查询执行时间,并且在某些情况下它会减慢速度。

创建测试表并用数据填充它的查询是:

CREATE TABLE [dbo].[IndexTestTable](
    [id] [int] IDENTITY(1,1) PRIMARY KEY,
    [Name] [nvarchar](20) NULL,
    [val1] [bigint] NULL,
    [val2] [bigint] NULL)

DECLARE @counter INT;
SET @counter = 1;

WHILE @counter < 500000
BEGIN
    INSERT INTO IndexTestTable
      (
        -- id -- this column value is auto-generated
        NAME,
        val1,
        val2
      )
    VALUES
      (
        'Name' + CAST((@counter % 100) AS NVARCHAR),
        RAND() * 10000,
        RAND() * 20000
      );

    SET @counter = @counter + 1;
END

-- Index in question
CREATE NONCLUSTERED INDEX [IndexA] ON [dbo].[IndexTestTable]
(
    [Name] ASC
)
INCLUDE (   [id],
    [val1],
    [val2])
Run Code Online (Sandbox Code Playgroud)

现在改进的查询 1(仅略有改进但改进是一致的)是:

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'
Run Code Online (Sandbox Code Playgroud)

没有索引的统计和执行计划(在这种情况下,表使用默认的聚集索引):

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 5580, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

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

在此处输入图片说明

现在启用索引:

(5000 row(s) affected)
Table 'IndexTestTable'. Scan count 2, logical reads 2819, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

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

在此处输入图片说明

现在由于索引而变慢的查询(查询没有意义,因为它只是为了测试而创建的):

SELECT I1.Name,
       SUM(I1.val1),
       SUM(I1.val2),
       MIN(I2.Name),
       SUM(I2.val1),
       SUM(I2.val2)
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.Name = I2.Name
WHERE   
       I2.Name = 'Name1'
GROUP BY
       I1.Name
Run Code Online (Sandbox Code Playgroud)

启用聚集索引:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 4, logical reads 60, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 1, logical reads 155106, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

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

在此处输入图片说明

现在禁用索引:

(1 row(s) affected)
Table 'IndexTestTable'. Scan count 5, logical reads 8642, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 2, logical reads 165212, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.
Table 'Worktable'. Scan count 0, logical reads 0, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

(1 row(s) affected)

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

在此处输入图片说明

问题是:

  1. 即使 SQL Server 建议使用索引,为什么它会因显着差异而减慢速度?
  2. 什么是占用大部分时间的嵌套循环连接以及如何改善其执行时间?
  3. 有什么我做错或错过了吗?
  4. 使用默认索引(仅在主键上)为什么它花费的时间更少,并且存在非聚集索引,对于连接表中的每一行,应该更快地找到连接表行,因为连接在名称列上索引已创建。这反映在查询执行计划上,当 IndexA 处于活动状态时,Index Seek 成本较低,但为什么仍然较慢?此外,嵌套循环左外连接中是什么导致速度变慢?

使用 SQL Server 2012

Pau*_*ite 29

即使 SQL Server 建议使用索引,为什么它会因显着差异而减慢速度?

索引建议由查询优化器提出。如果遇到现有索引不能很好地服务的表中的逻辑选择,它可能会向其输出添加“缺少索引”建议。这些建议是机会主义的;它们不是基于对查询的全面分析,也没有考虑更广泛的考虑。充其量,它们表明可能有更多有用的索引,熟练的 DBA 应该看看。

关于缺失索引建议的另一件事是,它们基于优化器的成本计算模型,优化器估计建议的索引可能会降低查询的估计成本的程度。这里的关键词是“模型”和“估计”。查询优化器对您的硬件配置或其他系统配置选项知之甚少——它的模型主要基于固定数字,大多数情况下,这些数字恰好为大多数系统上的大多数人产生合理的计划结果。除了所使用的确切成本数字存在问题之外,结果始终是估计值——而且估计值可能是错误的。

什么是占用大部分时间的嵌套循环连接以及如何改善其执行时间?

几乎不需要做任何事情来提高交叉连接操作本身的性能;嵌套循环是交叉连接唯一可能的物理实现。连接内侧的表假脱机是一种优化,以避免为每个外行重新扫描内侧。这是否是一种有用的性能优化取决于各种因素,但在我的测试中,没有它查询会更好。同样,这是使用成本模型的结果 - 我的 CPU 和内存系统可能具有与您不同的性能特征。没有特定的查询提示来避免表假脱机,但有一个未记录的跟踪标志 (8690),您可以使用它来测试有和没有假脱机的执行性能。如果这是一个真正的生产系统问题,可以使用基于启用 TF 8690 生成的计划的计划指南强制执行没有线轴的计划。不建议在生产中使用未记录的跟踪标志,因为安装在技术上不受支持,并且跟踪标志可能会产生不良副作用。

有什么我做错或错过了吗?

您缺少的主要内容是,尽管根据优化器的模型,使用非聚集索引的计划的估计成本较低,但它存在严重的执行时间问题。如果您使用聚集索引查看计划中跨线程的行分布,您可能会看到一个相当不错的分布:

扫描计划

在使用非聚集索引查找的计划中,工作最终完全由一个线程执行:

寻求计划

这是通过并行扫描/搜索操作在线程之间分配工作方式的结果。并行扫描并不总是比索引查找更好地分配工作 - 但在这种情况下确实如此。更复杂的计划可能包括重新分区交换以跨线程重新分配工作。这个计划没有这样的交换,所以一旦行被分配给一个线程,所有相关的工作都在同一个线程上执行。如果您查看执行计划中其他运算符的工作分配,您将看到所有工作都由同一个线程执行,如索引查找所示。

没有查询提示来影响线程之间的行分布,重要的是要意识到这种可能性并能够在执行计划中读取足够的细节来确定何时导致问题。

使用默认索引(仅在主键上)为什么它花费的时间更少,并且存在非聚集索引,对于连接表中的每一行,应该更快地找到连接表行,因为连接在名称列上索引已创建。这反映在查询执行计划上,当 IndexA 处于活动状态时,Index Seek 成本较低,但为什么仍然较慢?此外,嵌套循环左外连接中是什么导致速度变慢?

现在应该很清楚非聚集索引计划可能更有效,正如您所期望的那样;只是在执行时跨线程的工作分配不佳才导致了性能问题。

为了完成示例并说明我提到的一些事情,获得​​更好的工作分配的一种方法是使用临时表来驱动并行执行:

SELECT
    val1,
    val2
INTO #Temp
FROM dbo.IndexTestTable AS ITT
WHERE Name = N'Name1';

SELECT 
    N'Name1',
    SUM(T.val1),
    SUM(T.val2),
    MIN(I2.Name),
    SUM(I2.val1),
    SUM(I2.val2)
FROM   #Temp AS T
CROSS JOIN IndexTestTable I2
WHERE
    I2.Name = 'Name1'
OPTION (FORCE ORDER, QUERYTRACEON 8690);

DROP TABLE #Temp;
Run Code Online (Sandbox Code Playgroud)

这导致计划使用更高效的索引查找,不具有表假脱机功能,并且可以很好地跨线程分配工作:

最优方案

在我的系统上,此计划的执行速度明显快于 Clustered Index Scan 版本。

如果您有兴趣了解有关并行查询执行内部原理的更多信息,您可能想观看我的 PASS Summit 2013 会议记录