3

我在 SQL Server 2008 R2 数据库中有一个表

Article (Id, art_text)

Id 是主键。art_text 有一个全文索引。

我搜索包含“房子”一词的最新文章,如下所示:

SELECT TOP 100 Id, art_text 
FROM Article
WHERE CONTAINS(art_text, 'house')
ORDER BY Id DESC

这会返回正确的结果,但速度很慢(约 5 秒)。该表有 2000 万行,其中 350,000 行包含单词 house。我可以在查询计划中看到,在聚集索引中对全文索引返回的 350,000 个 Id 执行了索引扫描。

如果有办法只获取全文索引中包含单词“house”的最新 100 个条目,则查询可能会快得多。有什么方法可以使查询更快吗?

4

1 回答 1

8

简短的回答是肯定的,有一些方法可以让这个特定的查询变得更快,但是对于 2000 万行的语料库,5 秒还不错。您需要认真考虑以下建议是否最适合您的 FT 搜索工作负载,并权衡成本与收益。如果你盲目地实施这些,你将度过一段糟糕的时光。


提高 Sql Server 全文搜索性能的一般建议

减小正在搜索的全文索引的大小 FT 索引越小,查询越快。有几种方法可以减小 FT 索引大小。前两个可能适用也可能不适用,第三个需要大量工作才能完成。

  1. 添加特定领域的噪声词 噪声词是对全文搜索查询没有增值的词,例如“the”、“and”、“in”等。如果有与业务相关的词没有增值被索引,您可能会受益于将它们从 FT 索引中排除。考虑 MSDN 库上的假设全文索引。“Microsoft”、“library”、“include”、“dll”和“reference”等术语可能不会为搜索结果增加价值。(访问http://msdn.microsoft.com并搜索“microsoft”有什么实际价值吗?)法律意见的 FT 索引可能会排除“被告”、“起诉”和“法律”等词.

  2. 使用 iFilters 去除无关数据 使用 Windows iFilters 进行全文搜索以从二进制文档中提取文本。这与窗口搜索功能用于搜索 pdf 和 powerpoint 文档的技术相同。这特别有用的一种情况是当您有一个可以包含 HTML 标记的描述列时。默认情况下,Sql Server 全文搜索将对所有内容进行索引,因此您可以将“font-family”、“Arial”和“href”等词作为可搜索词。使用 HTML iFilter 可以去除标记。

    在 FT 索引中使用 iFilter 的两个要求是索引列是 VARBINARY 并且有一个包含文件扩展名的“类型”列。这两个都可以通过计算列来完成。

    CREATE TABLE t (
    ....
    description varbinary(max),
    FTS_description as (CAST(description as VARBINARY(MAX)),
    FTS_filetype as ( N'.html' )
    )
    -- Then create the fulltext index on FTS_description specifying the filetype.
    
  3. 索引表的部分并将结果拼接在一起 有几种方法可以完成此操作,但总体思路是将表拆分为更小的块,单独查询这些块并组合结果。例如,您可以创建两个索引视图,一个用于当前年份,一个用于历史年份,并带有全文索引。您返回 100 行的查询更改为如下所示:

    DECLARE @rows int
    DECLARE @ids table (id int not null primary key)
    
    INSERT INTO @ids (id)   
        SELECT TOP (100) id 
        FROM vw_2013_FTDocuments WHERE CONTAINS (....) 
        ORDER BY Id DESC 
    SET @rows = @@rowcount
    IF @rows < 100
    BEGIN
      DECLARE @rowsLeft int
      SET @rowsLeft = 100 - @rows
      INSERT INTO @ids (id) SELECT TOP (@rowsLeft) ......
      --Logic to incorporate the historic data
    END
    SELECT ... FROM t INNER JOIN @ids .....
    

    这可以以增加搜索逻辑的复杂性为代价来显着减少查询时间。当搜索通常仅限于数据的子集时,此方法也适用。例如,craigslist 可能有一个住房的 FT 索引,一个是“待售”,一个是“就业”。从主页完成的任何搜索都将从各个索引拼接在一起,而在一个类别内进行搜索的常见情况更有效。

不受支持的技术可能会在 Sql Server 的未来版本中中断。

您需要使用与生产相同数量和质量的数据进行广泛测试。如果在未来版本的 Sql server 中行为发生变化,您将无权投诉。这是基于观察,而不是证据。使用风险自负!!

全文历史记录 在 Sql Server 2005 中,全文搜索功能位于 sqlservr.exe 的外部进程中。FTS 被纳入查询计划的方式是作为一个黑盒。Sql server 将向 FTS 传递一个查询,FTS 将返回一个 id 流。这将 Sql Server 可用的计划限制为 FTS 运算符基本上可以被视为表扫描的计划。

在 Sql Server 2008 中,FTS 被集成到引擎中,从而提高了性能。它还为优化器提供了 FTS 查询计划的新选项。具体来说,它现在可以选择在 LOOP JOIN 运算符中探测 FTS 索引,以检查各个行是否与 FTS 谓词匹配。(请参阅http://sqlblog.com/blogs/joe_chang/archive/2012/02/19/ query-optimizer-gone-wild-full-text.aspx 对此进行了精彩的讨论以及事情可能出错的方式。)

对我们的最优 FTS 查询计划 的要求 为了获得最优查询计划,需要争取两个特征。

  1. 没有排序操作。排序很慢,我们不想对 2000 万行或 350,000 行进行排序。
  2. 不要返回与 FTS 谓词匹配的所有 350k 行。如果可能的话,我们需要避免这种情况。

这两个标准消除了任何带有哈希连接的计划,因为哈希连接需要消耗所有一个输入来构建哈希表。

对于带有循环连接的计划,有两种选择。向后扫描聚集索引,并为每一行探测全文搜索引擎以查看该特定行是否匹配。从理论上讲,这似乎是一个很好的解决方案,因为一旦我们匹配了 100 行,我们就完成了。我们可能必须尝试 10,000 个 id 才能找到匹配的 100 个,但这可能比读取所有 350k 更好。如果每个探针都很昂贵,那么情况可能会更糟(请参阅上面的 Joe Chang 博客链接),那么我们的 10k 探针可能比仅读取所有 350k 行花费的时间要长得多。

另一个循环连接选项是让 FTS 部分位于循环的外侧,并查找聚集索引。不幸的是,FTS 引擎不喜欢以相反的顺序返回结果,所以我们必须读取所有 350k,然后对它们进行排序以返回前 100 个。

障碍是让 FTS 引擎以相反的顺序返回行。 如果我们能克服这个问题,那么我们可以将 IO 减少到只读取匹配的最后 100 行。幸运的是,FTS 引擎倾向于按创建索引时指定的唯一索引的键顺序返回行。(这是 FTS 引擎使用的内部存储的自然副作用)

通过添加一个作为 id 负数的计算列,并在创建 FT 索引时在该列上指定一个唯一索引,那么我们真的很接近了。

CREATE TABLE t (id int not null primary key, txt varchar(max), neg_id as (-id) persisted )
CREATE UNIQUE INDEX IX_t_neg_id on t (neg_id)
CREATE FULLTEXT INDEX on t ( txt ) KEY INDEX IX_t_neg_id

现在对于我们的查询,我们将使用 CONTAINSTABLE 和一些 LEFT-join 技巧来确保 FTS 谓词不会出现在 LOOP JOIN 的内部。

SELECT TOP (100) t.id, t.txt 
FROM CONTAINSTABLE(t, txt, 'house') ft 
LEFT JOIN t on tf.[Key] = t.neg_id ORDER BY tf.[key]

生成的计划应该是一个循环连接,它只从 FT 索引中读取最后 100 行。

可以吹倒这座纸牌屋的小风:

  • 复杂的 FTS 查询(如在多个术语中或使用 NOT 或 OR 运算符可能导致 Sql 2008+ 变得“智能”并将逻辑转换为查询计划中加入的多个 FTS 查询。
  • 任何累积更新、Service Pack 或主要版本升级都可能使这种方法失效。
  • 它可能在 95% 的情况下有效,在剩余的 5% 情况下超时。
  • 它可能对你根本不起作用。

祝你好运!

于 2013-06-20T05:31:58.427 回答