这是基于成本的优化器的决定。
此选择中使用的估计成本是不正确的,因为它假设不同列中的值之间具有统计独立性。
这与“ 行进目标已消失”中描述的问题相似,其中偶数和奇数负相关。
易于复制。
CREATE TABLE dbo.animal(
id int IDENTITY(1,1) NOT NULL PRIMARY KEY,
colour varchar(50) NOT NULL,
species varchar(50) NOT NULL,
Filler char(10) NULL
);
/*Insert 20 million rows with 1% black and 1% swan but no black swans*/
WITH T
AS (SELECT TOP 20000000 ROW_NUMBER() OVER (ORDER BY @@SPID) AS RN
FROM master..spt_values v1,
master..spt_values v2,
master..spt_values v3)
INSERT INTO dbo.animal
(colour,
species)
SELECT CASE
WHEN RN % 100 = 1 THEN 'black'
ELSE CAST(RN % 100 AS VARCHAR(3))
END,
CASE
WHEN RN % 100 = 2 THEN 'swan'
ELSE CAST(RN % 100 AS VARCHAR(3))
END
FROM T
/*Create some indexes*/
CREATE NONCLUSTERED INDEX ix_species ON dbo.animal(species);
CREATE NONCLUSTERED INDEX ix_colour ON dbo.animal(colour);
现在尝试
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black'
AND species LIKE 'swan'
这给出了下面的计划,其成本为0.0563167
。
该计划能够在id
列上两个索引的结果之间执行合并联接。(这里有更多合并合并算法的细节)。
合并联接要求通过联接键对两个输入进行排序。
非聚集索引分别由(species, id)
和排序(colour, id)
(非唯一非聚集索引总是将行定位符隐式地添加到键的末尾(如果未显式添加)。没有任何通配符的查询正在对species = 'swan'
和执行相等搜索colour ='black'
。由于每次查找仅从前导列中检索一个确切值,id
因此将按顺序对匹配的行进行排序,因此该计划是可能的。
查询计划运算符从左到右执行。随着左操作者从它的孩子,这反过来从请求行请求行他们的孩子(依此类推,直到叶节点到达)。TOP
一旦收到10 ,迭代器将停止从其子级请求更多行。
SQL Server的索引统计信息告诉它1%的行与每个谓词匹配。假定这些统计信息是独立的(即没有正或负相关),因此平均而言,一旦处理了与第一个谓词匹配的1,000行,它将发现与第二个谓词匹配的10行并可以退出。(以上计划实际上显示的是987,而不是1,000,但足够接近)。
实际上,由于谓词负相关,因此实际计划表明,每个索引都需要处理所有200,000个匹配行,但这在某种程度上得到了缓解,因为零连接的行也意味着实际上需要零查找。
与之比较
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
给出下面的计划,其成本为 0.567943
尾随通配符的添加现在已引起索引扫描。尽管对2000万行表进行扫描,但该计划的成本仍然很低。
添加querytraceon 9130
显示更多信息
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 9130)
可以看出,SQL Server认为它只需要扫描大约100,000行,即可找到10个与谓词匹配的记录,并且TOP
可以停止请求行。
同样,对于独立性假设,这是有道理的 10 * 100 * 100 = 100,000
最后让我们尝试强制索引交叉计划
SELECT TOP 10 *
FROM animal WITH (INDEX(ix_species), INDEX(ix_colour))
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
这为我提供了一个并行计划,估计费用为3.4625
这里的主要区别是colour like 'black%'
谓词现在可以匹配多种不同的颜色。这意味着不再保证该谓词的匹配索引行按的顺序排序id
。
例如,索引seek on like 'black%'
可能返回以下行
+------------+----+
| Colour | id |
+------------+----+
| black | 12 |
| black | 20 |
| black | 23 |
| black | 25 |
| blackberry | 1 |
| blackberry | 50 |
+------------+----+
在每种颜色中,id是有序的,但是跨不同颜色的ID可能不是。
结果,SQL Server无法再执行合并联接索引交集(而无需添加阻塞排序运算符),而是选择执行哈希联接。哈希联接阻塞了构建输入,因此现在的成本反映了以下事实:所有匹配的行都需要从构建输入中进行处理,而不是像在第一个计划中那样假设它只需扫描1,000。
探针输入是非阻塞的,但是它仍然错误地估计在处理了987行之后它将能够停止探测。
(有关非阻塞与阻塞迭代器的更多信息,请点击此处)
考虑到额外估计的行和散列连接的成本增加,部分聚簇索引扫描看起来更便宜。
当然,在实践中,“部分”聚集索引扫描根本不是部分,它需要遍历整个2000万行,而不是比较计划时假定的10万行。
增加TOP
(或完全删除)值的过程最终会遇到一个临界点,在该临界点处,估计CI扫描将需要覆盖的行数使该计划看起来更昂贵,并且恢复为索引相交计划。这两个计划之间对我的截止点是TOP (89)
VS TOP (90)
。
对您来说,它可能会有所不同,因为它取决于聚簇索引的宽度。
删除TOP
并强制执行CI扫描
SELECT *
FROM animal WITH (INDEX = 1)
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
88.0586
我的示例表在我的机器上花费了。
如果SQL Server知道该动物园没有黑天鹅,并且需要进行全面扫描,而不是仅读取100,000行,则不会选择该计划。
我尝试了多列统计信息,animal(species,colour)
并尝试了animal(colour,species)
过滤统计信息,animal (colour) where species = 'swan'
但这些方法都无法说服黑天鹅不存在,并且TOP 10
扫描需要处理超过100,000行。
这是由于“包含假设”,SQL Server本质上假设,如果您正在搜索某些内容,则它可能存在。
在2008+上,有记录的跟踪标志4138关闭行目标。这样做的结果是,在不TOP
考虑子操作符将允许其尽早终止而不读取所有匹配行的假设的情况下对计划进行了计算。有了这个跟踪标志,我自然会得到更优化的索引交叉计划。
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 4138)
现在,该计划正确地花费了读取两个索引查找中的全部20万行的开销,但超过了关键查找的开销(估计为2000而TOP 10
不是实际的0。这会将其限制为最多10个,但是跟踪标志阻止了此考虑) 。该计划的成本仍然比完整CI扫描便宜得多,因此选择了该计划。
当然,这个计划可能不是最佳的哪些组合是常见的。如白天鹅。
在两种情况下animal (colour, species)
或在理想情况下,复合索引animal (species, colour)
将使查询的效率大大提高。
为了最有效地使用复合索引,LIKE 'swan'
还需要将其更改为= 'swan'
。
下表显示了所有四个排列的执行计划中显示的查找谓词和残差谓词。
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| WHERE clause | Index | Seek Predicate | Residual Predicate |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
| colour LIKE 'black%' AND species LIKE 'swan' | ix_colour_species | colour >= 'black' AND colour < 'blacL' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species LIKE 'swan' | ix_species_colour | species >= 'swan' AND species <= 'swan' | colour like 'black%' AND species like 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_colour_species | (colour,species) >= ('black', 'swan')) AND colour < 'blacL' | colour LIKE 'black%' AND species = 'swan' |
| colour LIKE 'black%' AND species = 'swan' | ix_species_colour | species = 'swan' AND (colour >= 'black' and colour < 'blacL') | colour like 'black%' |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
TOP
变量中的值意味着它将假设TOP 100
而不是TOP 10
。根据两个计划之间的临界点是什么,这可能有帮助,也可能没有帮助。