您的执行计划
查看查询计划时,我们可以看到触摸了一个索引即可提供两次过滤操作。
简而言之,由于使用了TOP运算符,因此设置了行目标。有关行目标的更多信息和前提条件,请参见此处
从同一来源:
行目标策略通常意味着优先于非阻塞导航操作(例如,嵌套循环联接,索引查找和查找),而不是基于阻塞,基于集合的操作(例如排序和散列)。只要客户可以从快速启动和稳定的行流中受益(这可能需要更长的总体执行时间,请参见上面的Rob Farley的帖子),这将很有用。还有更明显和传统的用法,例如一次显示结果。
使用具有行目标集的左半联接将整个表探查到过滤器中,以期尽可能快和高效地返回5行。
这不会发生,导致在.Fulltextmatch TVF上进行了许多迭代。
重新建立
根据您的计划,我能够在某种程度上重现您的问题:
CREATE TABLE dbo.Person(id int not null,lastname varchar(max));
CREATE UNIQUE INDEX ui_id ON dbo.Person(id)
CREATE FULLTEXT CATALOG ft AS DEFAULT;
CREATE FULLTEXT INDEX ON dbo.Person(lastname)
KEY INDEX ui_id
WITH STOPLIST = SYSTEM;
GO
INSERT INTO dbo.Person(id,lastname)
SELECT top(12000) ROW_NUMBER() OVER (ORDER BY (SELECT NULL)),
REPLICATE(CAST('A' as nvarchar(max)),80000)+ CAST(ROW_NUMBER() OVER (ORDER BY (SELECT NULL)) as varchar(10))
FROM master..spt_values spt1
CROSS APPLY master..spt_values spt2;
CREATE CLUSTERED INDEX cx_Id on dbo.Person(id);
运行查询
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 1 OR contains("lastName", '"B*"');
结果与您的查询计划相当:
在上面的示例中,全文索引中不存在B。结果,它取决于参数和数据查询计划的效率。
这方面的一个更好的解释中可以找到行的目标,第2部分:半加入由保罗·怀特
...换句话说,在应用的每次迭代中,我们都可以使用下推连接谓词在找到第一个匹配项后立即停止查看输入B。这正是行目标的优点:生成计划的一部分,该计划经过优化可快速返回前n个匹配行(此处n = 1)。
例如,更改谓词,以便更快地找到结果(在扫描开始时)。
select top (5) *
from dbo.Person
where "id" = 124
or contains("lastName", '"A*"');
将where "id" = 124
被淘汰,由于全文索引断言已经返回5行,满足TOP()
谓语。
结果也表明了这一点
id lastname
1 'AAA...'
2 'AAA...'
3 'AAA...'
4 'AAA...'
5 'AAA...'
TVF处决:
插入一些新行
INSERT INTO dbo.Person
SELECT 12001, REPLICATE(CAST('B' as nvarchar(max)),80000);
INSERT INTO dbo.Person
SELECT 12002, REPLICATE(CAST('B' as nvarchar(max)),80000);
运行查询以查找这些先前插入的行
SELECT TOP (2) *
from dbo.Person
where "id" = 1
or contains("lastName", '"B*"');
这再次导致几乎所有行的迭代次数过多,无法返回找到的最后一个值。
id lastname
1 'AAA...'
12001 'BBB...'
解决
使用traceflag 4138删除行目标时
SELECT TOP (5) *
FROM dbo.Person
WHERE "id" = 124
OR contains("lastName", '"B*"')
OPTION(QUERYTRACEON 4138 );
优化器使用更接近于实现a的联接模式UNION
,在我们的情况下,这是有利的,因为它将谓词下推至其各自的聚集索引查找,并且不使用针对行的左半联接运算符。
不使用上述traceflag的另一种写法:
SELECT top (5) *
FROM
(
SELECT *
FROM dbo.Person
WHERE "id" = 1
UNION
SELECT *
FROM dbo.Person
WHERE contains("lastName", '"B*"')
) as A;
生成的查询计划:
直接应用全文功能的地方
作为旁注,对于op,查询优化器修补程序traceflag 4199解决了他的问题。他通过添加OPTION(QUERYTRACEON(4199))
查询来实现这一点。我无法重现这种行为。此修复程序确实包含一个半联接优化:
跟踪标志:4102功能:SQL 9-如果查询的执行计划包含半联接运算符,则查询性能会降低。通常,当查询包含IN关键字或EXISTS关键字时,将生成半联接运算符。启用标志4102和4118可以克服此问题。
资源
额外
在基于成本的优化过程中,优化器还可以向执行计划中添加索引假脱机,由LogOp_Spool Index on fly Eager
(或物理对应物)实施
它针对我的数据集执行此操作,TOP(3)
但不针对TOP(2)
SELECT TOP (3) *
from dbo.Physician
where "id" = 1
or contains("lastName", '"B*"')
第一次执行时,急切的假脱机将读取并存储整个输入,然后再返回谓词所请求的行子集。以后的执行从工作表中读取并返回行的相同或不同子集,而不必执行子级节点。
资源
将搜寻谓词应用于该索引急切假脱机: