添加通配符时,SQL Server查询的速度显着下降(或顶部)


52

我有一个2千万只动物的动物园,可以在我的SQL Server 2005数据库中进行跟踪。其中约1%是黑色,其中约1%是天鹅。我想获取所有黑天鹅的详细信息,因此,不想淹没我所做的结果页面:

select top 10 * 
from animal 
where colour like 'black'  
and species like 'swan'

(是的,不建议您将这些字段设置为自由文本,但是它们都已编入索引)。事实证明,我们没有这种动物,因为查询会在大约300毫秒内返回空集。如果我使用'='而不是'like',它的速度大约是以前的两倍,但是我有一个预感,后者将为我节省一些输入时间。

事实证明,动物园的负责人认为他可能输入了一些“黑色”的天鹅,因此我相应地修改了查询:

select top 10 * 
from animal  
where colour like 'black%' 
and species like 'swan'

事实证明,这些都不存在(事实上,除了“黑色”动物之外,没有“黑色%”动物),但是查询现在大约需要30秒才能返回空值。

看来这只是“ top”和“ like%”的组合造成麻烦,尽管

select count(*) 
from animal  
where colour like 'black%' 
and species like 'swan'

很快返回0,甚至

select * 
from animal 
where colour like 'black%' 
and species like 'swan'

在不到一秒的时间内返回空值。

有谁知道为什么“ top”和“%”会共同造成如此巨大的性能损失,尤其是在空结果集中?

编辑:为了澄清,我没有使用任何FreeText索引,我只是意味着字段在输入点是自由文本,即未在数据库中标准化。很抱歉造成我的困惑,我的措辞不好。

Answers:


76

这是基于成本的优化器的决定。

此选择中使用的估计成本是不正确的,因为它假设不同列中的值之间具有统计独立性。

这与“ 行进目标已消失”中描述的问题相似,其中偶数和奇数负相关。

易于复制。

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%'                         |
+----------------------------------------------+-------------------+----------------------------------------------------------------+----------------------------------------------+

15

引起了我的兴趣,我进行了一些搜索并偶然发现了这个Q / A。TOP 如何(以及为什么)影响执行计划?

基本上,使用TOP会改变操作员的成本(以不平凡的方式),这也会导致总体计划也发生变化(如果您包括带有和不带有TOP 10的ExecPlans,那将是一个很好的选择),这几乎会改变TOP的总体执行查询。

希望这可以帮助。

例如,我在数据库上尝试过:-当没有调用top时,使用并行性-对于TOP,不使用并行性

因此,再次显示您的执行计划将提供更多信息。

祝你今天愉快


-1

我相信这可能是由于MSSQL 2005的基本特性以及查询优化器确定哪种执行计划最有效的方式。

如果使用SQL变量,它将“欺骗”查询优化器以使用哈希匹配而不是嵌套循环,这将导致更高程度的并行性。

尝试:

DECLARE @topn INT = 10
SELECT TOP (@topn) *
FROM    animal
WHERE   colour LIKE 'black%' 
AND species LIKE 'swan'

5
混淆TOP变量中的值意味着它将假设TOP 100而不是TOP 10。根据两个计划之间的临界点是什么,这可能有帮助,也可能没有帮助。
马丁·史密斯
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.