为什么 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 > 0
和 SELECT Salary FROM Person
上的估计行数(因此成本)相同,计划的成本均为 50%(因为计划的成本是估计成本,即使在实际计划中)
我有一个测试 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 副本)它更快从索引中获取所有需要的数据。
其次:> 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 > 0
和 SELECT Salary FROM Person
上的估计行数(因此成本)相同,计划的成本均为 50%(因为计划的成本是估计成本,即使在实际计划中)