为什么 Salary >0 不会导致 table / index 扫描

Why is Salary >0 not causing a table / index scan

我有一个测试 table 这个练习:

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
)

如果我 运行 以下内容,我会得到一个 table 扫描,这正是我所期望的

SELECT Salary FROM Person

如果我这样做,我会进行索引搜索 - 再次符合预期,

SELECT Salary FROM Person WHERE Salary > 270

但是,如果我这样做:

SELECT Salary FROM Person WHERE Salary > 0

我进行了索引查找(尽管它返回了 table

中的所有行

此外,如果我 运行

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

同一批次,都是50%的批次

这是怎么回事? 如果返回所有行,为什么 SQL 服务器在 WHERE 子句存在时使用查找?

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

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

查询仅检索单个列 Person

SELECT Salary FROM Person WHERE Salary > 0 

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

如果salary 列上有索引,那么整体上只扫描这个索引会更便宜table。对于此查询,这就是所谓的 ,因为索引包含执行此查询所需的所有信息,数据库从索引文件中读取所有需要的信息,而不会到达 table。

这里发生了几件事:

首先:没有 table 扫描,因为您的数据位于非聚簇索引中(将其视为较小的 - 有序 - 仅包含薪水的 table 副本)它更快从索引中获取所有需要的数据。

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

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

下面的代码演示在我的环境中以 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 以获得完整扫描。

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

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

If I run the following, I get a table scan which is what I would expect

应该阅读

I get a non-clustered index scan which is what I expect

这是因为作为查询

SELECT Salary FROM Person

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

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

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

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

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

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