为什么Salary> 0不会导致表/索引扫描

时间:2018-05-04 15:43:59

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

我有一个测试表来进行这项练习:

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%

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

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

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

3 个答案:

答案 0 :(得分:0)

这里发生了一些事情:

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

其次:> 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才能获得完整扫描。

答案 1 :(得分:0)

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

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

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

应阅读

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

这是因为作为查询

SELECT Salary FROM Person

将扫描最窄的相关索引,在这种情况下为IX_Person_Salary

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

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

如上一篇文章中所述,查找基本上是对索引的扫描,在该索引中我们知道将从哪一行开始扫描,当值不再与谓词匹配时,它将停止扫描(这可能是通过WHERE子句意味着我们扫描从Salary = 270开始的索引,并从那里读取所有大于270的值(即所有IE)。在AND Salary < n子句中进一步WHERE的情况下,一旦我们在索引中击中n,搜索将停止读取。

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

由于SELECT Salary FROM Person WHERE Salary > 0SELECT Salary FROM Person上的估计行数(以及因此的成本)是相同的,所以计划都将花费50%(因为计划的成本是估计的成本,甚至根据实际计划)

答案 2 :(得分:-1)

查询仅检索单个列Person

SELECT Salary FROM Person WHERE Salary > 0 

同时只有一个条件Salary > 0使用相同的列Person
如果工资列上有索引,那么在整个表上只扫描此索引会更便宜。对于此查询,这称为covering index,因为索引包含执行此查询所需的所有信息,数据库将从索引文件中读取所有需要的信息,而不会到达表。