【问题标题】:Fast full-text search using String.IndexOf or MS SQL使用 String.IndexOf 或 MS SQL 进行快速全文搜索
【发布时间】:2019-07-05 10:07:43
【问题描述】:

我有一种情况需要超快速的全文通配符搜索。

以前我只使用一个 SQL 存储过程,它可以对具有多个连接的表执行搜索,并使用带有 LIKE '%searchTerm%' 的查询,但是它在几百万条记录上非常慢。

我尝试过在 SQL 中进行全文索引和搜索,但是,这似乎不起作用,因为它会中断单词,但我需要搜索字符串的中间。

更改为一个新的 SQL 存储过程,它将所有搜索字段连接到一个字符串中,并将其与对象 ID 的另一列一起返回——然后将整个对象缓存为 C# 中的 List<>(特别是作为AppPool 中的静态对象)并使用检查搜索字符串的 IndexOf() 的逻辑似乎大大提高了性能(从大约 10 秒到大约 100 毫秒)

我担心这是否是一种糟糕的方法,或者是否有更好的方法?

创建与对象 ID 关联的搜索字符串的新 SQL 存储过程如下;

CREATE PROCEDURE [dbo].[Search_GetLookupTable]
AS
BEGIN

    SELECT
        ObjectId,
        (Name + ' ' + OtherName + ' ' + ep.SomethingElse + ISNULL(

            (

            SELECT
                    ' ' + twl.SomeBindingName
                FROM
                    TableWithLotsOfBindings twl
                WHERE 
                    twl.ObjectId = e.ObjectId
                FOR XML PATH('')

            )

        , '')) AS SearchString,
        ep.LastActionDateTime AS OrderDate
    FROM
        ObjectTable e
        INNER JOIN ObjectMetaData ep ON ep.ObjectId = e.ObjectId
END 
GO

然后将其加载到具有ObjectIdSearchString 的模型的List<> 中。然后我将其保存到搜索 class 中的 static 属性中,然后跟踪最后一次加载 DateTime 并每 10 分钟左右回收一次。

我最初也将它保存到分布式内存缓存中,但是序列化和传输数据的性能很差。

private static readonly List<GlobalSearchLookupModel> _CachedSearchLookupModel = new List<GlobalSearchLookupModel>();
private static DateTime _CacheSearchLookupModelDateTime = DateTime.MinValue;

List<GlobalSearchLookupModel> lookupModels = _CachedSearchLookupModel.Value;

if (lookupModels == null)
{
   lookupModels = SqlClass.SearchLookupTable();
   _CachedSearchLookupModel.Value.Clear();
   _CachedSearchLookupModel.Value.AddRange(lookupModels);
   _CachedSearchLookupModelDateTime = DateTime.UtcNow;
}

if (_CacheSearchLookupModelDateTime.AddMinutes(10) <= DateTime.UtcNow)
{
   _CachedSearchLookupModelDateTime = DateTime.UtcNow;
   BackgroundTaskHelper.StartBackgroundTask(
   "Pre-Load Global Search Lookup Table",
   () =>
   {
      DateTime asyncPreloadStartDateTime = DateTime.UtcNow;
      _CachedSearchLookupModel.Value.Clear();
      _CachedSearchLookupModel.Value.AddRange(SqlClass.SearchLookupTable());
      _CachedSearchLookupModelDateTime = DateTime.UtcNow;

   });
}

List<Int64> ids = lookupModels
   .Where(l => l.SearchString.IndexOf(searchTerms, StringComparison.InvariantCultureIgnoreCase) >= 0)
   .OrderByDescending(l => l.OrderDate)
   .Select(l => l.ObjectId)
   .Distinct()
   .Take(maxReturn)
   .ToList();

【问题讨论】:

  • 您是否尝试过使用专门用于全文搜索的非 SQL 解决方案?比如 Elasticsearch 还是纯 Lucene?另外,看看位图索引,如果你索引 ngrams 并且你的文本是自然的(没有散列,垃圾),你可以大大加快速度
  • 我还没有尝试过 ElasticSearch 或其他任何东西。我去看看,谢谢。
  • 你能详细说明为什么全文索引不起作用?
  • @Steve 全文索引不起作用,因为它不会在 String 的中间进行搜索。 SQL 中唯一有效的方法是使用带有LIKE '%term%' 的查询
  • @Sivart 我很确定全文索引可以在字符串中间进行搜索。能举个具体的例子吗?

标签: c# sql-server asp.net-web-api search full-text-search


【解决方案1】:

据我所知,在任何现代 SQL 数据库中都没有这样的解决方案。它们根本不是为像您这样的用例而设计的。

对于这样的解决方案,您确实需要查看像 Elastic SearchAzure Search 这样的解决方案,即使这些解决方案也需要您使用特殊的构造和过滤器来实现纯通配符搜索功能。

【讨论】:

    【解决方案2】:

    您可以尝试使用以下想法在 SQL 中构建自己的搜索索引:

    假设你的桌子是

    MyTable(Id bigint Primary Key, Text nvarchar(max))

    ,其中Text 是您要搜索的列。

    然后建一个表

    IndexTable(TextIndex nvarchar(max) Primary Key, Id bigint)
    

    IdMyTable 的外键。

    现在您使用 MyTable 中包含的文本的所有后缀填充该表。

    您现在可以重写您的查询

    SELECT * 
    FROM MyTable 
    WHERE Text LIKE '%searchTerm%'
    
    to
    
    SELECT * 
    FROM MyTable 
    WHERE Id IN (SELECT Id FROM IndexTable WHERE TextIndex LIKE 'searchTerm%')
    

    (此查询也可以使用连接编写,但可能会导致重复)

    这应该是一个高效的查询,因为LIKE 'searchTerm%' 可以使用 IndexTable 的 PK-Index。

    最后一点,您可以使用触发器使该表保持最新。

    【讨论】:

      猜你喜欢
      • 2014-06-22
      • 2012-05-07
      • 1970-01-01
      • 2021-09-26
      • 1970-01-01
      • 1970-01-01
      • 2010-09-14
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多