sto*_*roz 52 sql-server-2005 sql-server
我有一个由 2000 万只动物组成的动物园,我在 SQL Server 2005 数据库中对其进行跟踪。其中约 1% 是黑色,约 1% 是天鹅。我想获得所有黑天鹅的详细信息,因此不想淹没我所做的结果页面:
select top 10 *
from animal
where colour like 'black'
and species like 'swan'
Run Code Online (Sandbox Code Playgroud)
(是的,不明智的是,这些字段是自由文本,但它们都已编入索引)。事实证明我们没有这样的动物,因为查询在大约 300 毫秒内返回一个空集。如果我使用 '=' 而不是 'like',它的速度会快两倍,但我有一种预感,后者将为我节省一些打字的时间。
原来动物园管理员认为他可能将一些天鹅输入为“黑色”,因此我相应地修改了查询:
select top 10 *
from animal
where colour like 'black%'
and species like 'swan'
Run Code Online (Sandbox Code Playgroud)
结果证明它们都没有(实际上除了“黑色”动物之外没有“黑色%”动物)但查询现在需要大约 30 秒才能返回空。
似乎只是 'top' 和 'like %' 的组合引起了麻烦,因为
select count(*)
from animal
where colour like 'black%'
and species like 'swan'
Run Code Online (Sandbox Code Playgroud)
返回 0 非常快,甚至
select *
from animal
where colour like 'black%'
and species like 'swan'
Run Code Online (Sandbox Code Playgroud)
在几分之一秒内返回空。
有没有人知道为什么 'top' 和 '%' 应该合谋导致如此巨大的性能损失,尤其是在空结果集中?
编辑:澄清一下,我没有使用任何 FreeText 索引,我只是意味着这些字段在入口点是自由文本,即未在数据库中标准化。很抱歉造成混乱,我的措辞不好。
Mar*_*ith 76
这是基于成本的优化器的决定。
此选择中使用的估计成本不正确,因为它假定不同列中的值之间具有统计独立性。
它类似于RowGoals Gone Rogue中描述的问题,其中偶数和奇数呈负相关。
很容易重现。
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);
Run Code Online (Sandbox Code Playgroud)
现在试试
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black'
AND species LIKE 'swan'
Run Code Online (Sandbox Code Playgroud)
这给出了以下成本为 的计划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'
Run Code Online (Sandbox Code Playgroud)
这给出了以下计划,其成本为 0.567943

添加尾随通配符现在导致了索引扫描。尽管对 2000 万行表进行扫描,该计划的成本仍然很低。
添加querytraceon 9130显示更多信息
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 9130)
Run Code Online (Sandbox Code Playgroud)

可以看出,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'
Run Code Online (Sandbox Code Playgroud)
这为我提供了一个并行计划,估计成本为 3.4625

这里的主要区别是colour like 'black%'谓词现在可以匹配多种不同的颜色。这意味着该谓词的匹配索引行不再保证按 的顺序排序id。
例如,索引查找like 'black%'可能会返回以下行
+------------+----+
| Colour | id |
+------------+----+
| black | 12 |
| black | 20 |
| black | 23 |
| black | 25 |
| blackberry | 1 |
| blackberry | 50 |
+------------+----+
Run Code Online (Sandbox Code Playgroud)
在每种颜色中,id 是有序的,但不同颜色的 id 可能不是。
因此,SQL Server 无法再执行合并连接索引交集(不添加阻塞排序运算符),而是选择执行散列连接。Hash Join 阻塞了构建输入,所以现在成本反映了这样一个事实,即所有匹配的行都需要从构建输入中进行处理,而不是像第一个计划那样假设它只需要扫描 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'
Run Code Online (Sandbox Code Playgroud)
88.0586在我的机器上为我的示例表计算成本。
如果 SQL Server 知道动物园没有黑天鹅,并且需要进行完整扫描而不是仅读取 100,000 行,则不会选择此计划。
我试着多列统计animal(species,colour)和animal(colour,species)和过滤统计animal (colour) where species = 'swan',但没有这些帮助,说服它是黑天鹅事件不存在,并且TOP 10扫描需要处理超过10万行。
这是由于“包含假设”,其中 SQL Server 本质上假设如果您正在搜索它可能存在的东西。
在 2008+ 上,有记录的跟踪标志 4138可关闭行目标。这样做的结果是计划的成本没有假设TOP将允许子运算符提前终止而不读取所有匹配的行。有了这个跟踪标志,我自然会得到更优化的索引交叉计划。
SELECT TOP 10 *
FROM animal
WHERE colour LIKE 'black%'
AND species LIKE 'swan'
OPTION (QUERYTRACEON 4138)
Run Code Online (Sandbox Code Playgroud)

该计划现在正确地读取两个索引查找中的完整 200,000 行的成本,但超过了键查找的成本(估计 2000 与实际 0。这TOP 10会将其限制为最多 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%' |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+
Run Code Online (Sandbox Code Playgroud)
Vla*_*sak 15
发现这个有趣的问题,我做了一些搜索并偶然发现了这个 Q/A TOP 如何(以及为什么)影响执行计划?
基本上,使用 TOP 会改变跟随它的操作员的成本(以一种非平凡的方式),这会导致整体计划也发生变化(如果您包括 ExecPlans 和不包括 TOP 10,那就太好了),这几乎改变了整体执行查询。
希望这可以帮助。
例如,我在数据库上尝试了它,并且: - 当没有调用 top 时,使用并行性 - 使用 TOP 时,不使用并行性
因此,再次显示您的执行计划将提供更多信息。
祝你今天过得愉快
| 归档时间: |
|
| 查看次数: |
3885 次 |
| 最近记录: |