【问题标题】:Why is Salary >0 not causing a table / index scan为什么 Salary > 0 不会导致表/索引扫描
【发布时间】:2018-10-15 03:54:26
【问题描述】:

我有这个练习的测试表:

CREATE DATABASE QueryTest
GO
USE QueryTest

CREATE TABLE Person
(
    ID INT IDENTITY (1,1),
    FirstName   NVARCHAR(50),
    SurName     NVARCHAR(50),
    Salary      MONEY
)

INSERT INTO Person
SELECT  TOP 2000
        FirstName,
        LastName,
        RAND(CAST( NEWID() AS varbinary)) *100000
FROM    [AdventureWorks2014].[Person].[Person]
ORDER   BY NEWID()

CREATE INDEX IX_Person_Salary ON Person
(
    Salary
)

如果我运行以下命令,我会得到一个表扫描,这是我所期望的

SELECT Salary FROM Person

如果我这样做,我会得到一个索引搜索 - 再次完全符合预期,

SELECT Salary FROM Person WHERE Salary > 270

但是,如果我这样做:

SELECT Salary FROM Person WHERE Salary > 0

我得到一个索引搜索(尽管它返回了表中的所有行

此外,如果我运行

SELECT Salary FROM Person
SELECT Salary FROM Person WHERE Salary > 0

在同一批次中,它们都是批次的 50%

这里发生了什么? 如果将返回所有行,为什么当 WHERE 子句存在时 SQL Server 使用查找?

为什么索引查找与索引扫描的成本相同?

我的印象是 SQL Server 会使用其统计信息来估计要返回的行数,然后相应地计划其执行。统计数据会告诉它 >0 是所有行,因此在这种情况下扫描成本会更低?

【问题讨论】:

  • 您是否在执行之间清除缓冲区并计划缓存?也许你有"forced parametrization option"。
  • 我认为这是意料之中的,因为引擎仍然需要比较薪水的值来查看它是否大于零,就像它与 270 比较时一样
  • 不应该吗?即使它返回所有内容,它仍然是一个搜索,因为它仍然需要解析索引。由于查找只触及符合条件的行并解析所有包含这些行的行,因此成本与符合条件的行数和解析数成正比,而不是与表中的总行数成正比。即使所有东西都被拉出,情况也是如此。除非我完全脱离基地。
  • 我的印象是 SQL Server 会使用其统计信息来估计要返回的行数,然后相应地计划其执行。统计数据会告诉它 >0 是所有行,因此在这种情况下扫描成本会更低?
  • 如果你有 100k 行和最新更新的统计数据会有什么不同吗?

标签: sql sql-server-2014 sql-execution-plan


【解决方案1】:

这里发生了几件事:

首先:没有表扫描,因为您的数据位于非集群索引中(将其视为仅包含 Salary 的表的较小有序副本)从索引中获取所需的所有数据会更快。

其次:> 0 事物和性能分裂。

[Salary] 的列定义允许 NULL。当 SQL 生成执行计划时,它假设 NULL 可能在表中,因此无法明确预测 >0 将返回所有值。 SQL“计划”进行搜索,但最终“实际上”进行了扫描。实际执行计划是估计执行计划,但有额外的指标。

下面的代码演示显示了这种行为,在我的环境中拆分为 52% 和 48%。

CREATE TABLE #TMP1
(
    ID INT IDENTITY (1,1),
    FirstName   NVARCHAR(50),
    SurName     NVARCHAR(50),
    Salary      MONEY
)

CREATE TABLE #TMP2
(
    ID INT IDENTITY (1,1),
    FirstName   NVARCHAR(50),
    SurName     NVARCHAR(50),
    Salary      MONEY NOT NULL
)
GO

INSERT INTO #TMP1
SELECT  'xxxxx','xxxxx',RAND(CAST( NEWID() AS varbinary)) *100000
GO 2000


INSERT INTO #TMP2
SELECT FirstName,SurName,Salary FROM #TMP1


CREATE INDEX IX_Person_Salary ON #TMP1
(
    Salary
)
CREATE INDEX IX_Person_Salary ON #TMP2
(
    Salary
)


SELECT Salary FROM #TMP1 WHERE Salary > 0
SELECT Salary FROM #TMP2 WHERE Salary > 0

更新

检查索引的直方图,如果它从 0 开始,那么您需要 >=0 才能获得完整扫描。

【讨论】:

【解决方案2】:

查询仅检索单个列 Person:

SELECT Salary FROM Person WHERE Salary > 0 

同时只有一个条件Salary > 0使用同一列Person。

如果在salary列上有索引,那么只扫描这个索引而不是在整张桌子。对于这个查询,这就是所谓的covering index,因为索引包含执行这个查询所需的所有信息,数据库从索引文件中读取所有需要的信息而不到达表。

【讨论】:

  • 我还希望对覆盖索引进行索引扫描,但 OP 正在查看索引搜索。
【解决方案3】:

我问这个问题已经有几年了,但我想我现在可以回答我自己的问题了。 (我还读了一些其他的答案,现在对我来说更有意义了,如果我在下面重复它们,我深表歉意)

首先,我认为我的问题中存在拼写错误/术语误用:

如果我运行以下命令,我会得到一个表扫描,这就是我想要的 期待

应该阅读

我得到了我所期望的非聚集索引扫描

这是因为作为查询

SELECT Salary FROM Person

将扫描最窄的相关索引,在本例中为IX_Person_Salary

根据Brent's article,扫描意味着我们从索引的一端开始读取(在这种情况下,读取到末尾)

以下查询产生索引查找 SELECT Salary FROM Person WHERE Salary > 270

正如上面文章中提到的,seek 基本上是对索引的扫描,我们知道将从哪一行开始扫描,当值不再与谓词匹配时它将停止扫描(这可能是通过一个索引或一直到最后)WHERE 子句意味着我们从Salary = 270 开始扫描该索引并从那里读取所有> 270 的值(即所有这些值)如果我们还有一个AND Salary < n在我们的WHERE 子句中,一旦我们在索引中点击n,seek 就会停止读取。

SELECT Salary FROM Person WHERE Salary > 0 也会导致搜索但是,搜索实际上是一个完整的索引扫描,因为它将从 Salary > 0 开始并扫描所有薪水 > 0 的值(即所有这些值) 这实际上是与SELECT Salary FROM Person 查询上的非聚集索引扫描相同,可以通过两个查询上读取的实际行数在各自的计划中相同来验证

由于 SELECT Salary FROM Person WHERE Salary > 0 和 SELECT Salary FROM Person 上的估计行数(因此成本)相同,因此计划的成本均为 50%(因为计划的成本是估计成本,即使在实际计划中也是如此)

【讨论】:

    猜你喜欢
    • 2021-09-14
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    • 1970-01-01
    • 2015-01-27
    • 2018-06-18
    • 2010-09-06
    • 2011-09-25
    相关资源
    最近更新 更多