我有一个测试表来进行这项练习:
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是所有行,因此在这种情况下扫描成本会更低?
答案 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 > 0
和SELECT Salary FROM Person
上的估计行数(以及因此的成本)是相同的,所以计划都将花费50%(因为计划的成本是估计的成本,甚至根据实际计划)
答案 2 :(得分:-1)
查询仅检索单个列Person
:
SELECT Salary FROM Person WHERE Salary > 0
同时只有一个条件Salary > 0
使用相同的列Person
如果工资列上有索引,那么在整个表上只扫描此索引会更便宜。对于此查询,这称为covering index,因为索引包含执行此查询所需的所有信息,数据库将从索引文件中读取所有需要的信息,而不会到达表。