索引不能使执行速度更快,并且在某些情况下会降低查询速度。为什么会这样呢?


34

我正在尝试使用索引来加快处理速度,但是在进行联接的情况下,索引并不能改善查询的执行时间,在某些情况下,它会降低处理速度。

创建测试表并填充数据的查询为:

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])

现在查询1,它得到了改进(只有一点点,但改进是一致的):

SELECT *
FROM   IndexTestTable I1
       JOIN IndexTestTable I2
            ON  I1.ID = I2.ID
WHERE  I1.Name = 'Name1'

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

(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.

在此处输入图片说明

现在启用索引:

(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.

在此处输入图片说明

现在,由于索引的原因,查询变慢了(查询是无意义的,因为它仅用于测试):

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

启用聚集索引后:

(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.

在此处输入图片说明

现在禁用索引:

(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.

在此处输入图片说明

问题是:

  1. 即使SQL Server建议使用索引,但为什么索引速度却会显着降低呢?
  2. 什么是大多数情况下需要使用的嵌套循环连接以及如何缩短其执行时间?
  3. 我做错了什么或错过了吗?
  4. 使用默认索引(仅在主键上),为什么要花更少的时间,而对于存在非聚集索引的连接表,则对于连接表中的每一行,应该更快地找到已连接表的行,因为连接位于“名称”列上索引已创建。这反映在查询执行计划中,并且当IndexA处于活动状态时,“索引查找”成本会降低,但是为什么还要慢一些?嵌套循环左外部联接中导致减速的原因是什么?

使用SQL Server 2012

Answers:


23

即使SQL Server建议使用索引,但为什么索引速度却会显着降低呢?

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

关于丢失索引建议的另一句话是,它们基于优化程序的成本核算模型,并且优化程序以建议的索引可减少查询的估计成本的程度进行估算。这里的关键词是“模型”和“估计”。查询优化器对您的硬件配置或其他系统配置选项一无所知-它的模型主要基于固定数字,该数字在大多数时间大多数情况下都能为大多数人提供合理的计划结果。除了所使用的确切成本数字存在问题外,结果始终是估算值-估算值可能是错误的。

什么是大多数情况下需要使用的嵌套循环连接以及如何缩短其执行时间?

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

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

您缺少的主要内容是,尽管根据优化程序的模型,使用非聚集索引的计划的估计成本较低,但它存在很大的执行时间问题。如果使用聚集索引查看计划中线程之间的行分布,则可能会看到合理的分布:

扫描计划

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

寻求计划

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

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

使用默认索引(仅在主键上),为什么要花更少的时间,而对于存在非聚集索引的连接表,则对于连接表中的每一行,应该更快地找到已连接表的行,因为连接位于“名称”列上索引已创建。这反映在查询执行计划中,并且当IndexA处于活动状态时,“索引查找”成本会降低,但是为什么还要慢一些?嵌套循环左外部联接中导致减速的原因是什么?

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

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

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;

这导致计划使用更有效的索引查找,不具有表后台处理程序,并在线程之间很好地分配工作:

最佳计划

在我的系统上,此计划的执行速度比“聚簇索引扫描”版本快得多。

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


0

这实际上不是索引的问题,它是写得不好的查询。您只有100个唯一的名称值,每个名称留下5000个唯一值。

因此,对于表1中的每一行,您将从表2中加入5000。您可以说25020004行。

尝试此操作,请注意,此索引仅包含您列出的1个索引。

    DECLARE @Distincts INT
    SET @Distincts = (SELECT  TOP 1 COUNT(*) FROM IndexTestTable I1 WHERE I1.Name = 'Name1' GROUP BY I1.Name)
    SELECT I1.Name
    , @Distincts
    , SUM(I1.val1) * @Distincts
    , SUM(I1.val2) * @Distincts
    , MIN(I2.Name)
    , SUM(I2.val1)
    , SUM(I2.val2)
    FROM   IndexTestTable I1
    LEFT OUTER JOIN

    (
        SELECT I2.Name
        , SUM(I2.val1) val1
        , SUM(I2.val2) val2
        FROM IndexTestTable I2
        GROUP BY I2.Name
    ) I2 ON  I1.Name = I2.Name
    WHERE I1.Name = 'Name1'
    GROUP BY  I1.Name

时间:

    SQL Server parse and compile time: 
       CPU time = 0 ms, elapsed time = 8 ms.
    Table 'IndexTestTable'. Scan count 1, logical reads 31, physical reads 0, read-ahead reads 0, lob logical reads 0, lob physical reads 0, lob read-ahead reads 0.

     SQL Server Execution Times:
       CPU time = 0 ms,  elapsed time = 1 ms.

    (1 row(s) affected)
    Table 'IndexTestTable'. Scan count 2, logical reads 62, 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.

     SQL Server Execution Times:
       CPU time = 16 ms,  elapsed time = 10 ms.

在此处输入图片说明

您不能将SQL索引归咎于格式错误的查询


1
感谢您的回答,是的,查询可以改进,但是我的问题的逻辑是对于默认索引(仅在主键上),为什么要花较少的时间,并且对于存在的非聚集索引,每一行在联接表中,应更快地找到联接表行,这反映在查询执行计划中,并且当IndexA处于活动状态时,“索引查找”成本较低,但是为什么还要慢一些?嵌套循环左外部联接中导致减速的原因是什么?我已编辑问题以添加此评论,以使问题更清楚。
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.