从 SQL Server 全文索引中获取前 n 个最新条目

Posted

技术标签:

【中文标题】从 SQL Server 全文索引中获取前 n 个最新条目【英文标题】:Getting top n latest entries from SQL Server full text index 【发布时间】:2013-06-18 22:01:03 【问题描述】:

我在 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 个包含房子这个词。我可以在查询计划中看到,在聚集索引中对全文索引返回的 350,000 个 Id 执行了索引扫描。

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

【问题讨论】:

只是想知道聚集索引有什么需要。 另外,请阅读本文了解如何调整全文索引以更快地工作simple-talk.com/sql/learn-sql-server/… 感谢文章指针。至于聚集索引,我不明白这个问题。它是 Id 上的聚集索引。你觉得没必要吗? 【参考方案1】:

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


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

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

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

    使用 iFilter 去除无关数据 使用 Windows iFilter 进行全文搜索以从二进制文档中提取文本。这与窗口搜索功能用于搜索 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.
    

    索引表的部分并将结果拼接在一起 有几种方法可以完成此操作,但总体思路是将表拆分为更小的块,单独查询这些块并组合结果。例如,您可以创建两个索引视图,一个用于当前年份,一个用于历史年份,并带有全文索引。您返回 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 查询计划的要求要获得最佳查询计划,需要争取两个特征。

    无排序操作。排序很慢,我们不想对 2000 万行或 350,000 行进行排序。 不要返回与 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% 情况下超时。 它可能根本不适合您。

祝你好运!

【讨论】:

非常感谢您非常有见地的回答。不受支持的技术是我正在寻找的技术。我注意到,如果我按升序查询键,则使用 CONTAINSTABLE 的查询正在寻找 100 行而不是索引扫描。我曾尝试使用按降序排列的主键,但这根本没有帮助。使用负数应该可以。我将在接下来的几天内对此进行测试,如果可行,请接受您的回答。我也会尝试您的建议 3。建议 1 和 2 对我没有帮助,但很高兴知道。我希望我可以投票给你的答案,但我没有 15 的声誉。 我想过建议一个降序索引,但我自己没有尝试过。 Sql 2012 包含很多全文搜索的性能改进,因此升级也可以为您带来一些性能改进。 只是一个小而重要的说明。如果您要添加此答案中描述的计算列,并且原始列是 Nvarchar(请注意“N”),则必须添加前导 00xFFFE,例如 FTS_description as 0xFFFE + (CAST(description as VARBINARY(MAX))) 请参阅我的答案 ***.com/questions/51555538/… 以获取更多详细信息

以上是关于从 SQL Server 全文索引中获取前 n 个最新条目的主要内容,如果未能解决你的问题,请参考以下文章

移动/复制 SQL Server 2005 全文索引

SQL Server 全文索引介绍(转载)

SQL Server 使用全文索引进行页面搜索

SQL Server 全文搜索

SQL Server中的全文搜索

SQL Server 全文索引的管理