【问题标题】:Database/datasource optimized for string matching?为字符串匹配优化的数据库/数据源?
【发布时间】:2013-02-08 02:32:30
【问题描述】:

我想存储大量(~数千个)字符串并能够使用通配符执行匹配。

例如,这里是一个示例内容:

  • Folder1
  • Folder1/Folder2
  • Folder1/*
  • Folder1/Folder2/Folder3
  • Folder2/Folder*
  • */Folder4
  • */Fo*4

(每一行也有额外的数据,比如标签,但匹配只针对那个键)

这是我想与数据匹配的示例:

  • Folder1
  • Folder1/Folder2/Folder3
  • Folder3

*在这里是通配符,可以是不同的字符)

我天真地考虑将它存储在 MySQL 表中并使用 % 通配符和 LIKE 运算符,但 MySQL 索引仅适用于通配符左侧的字符,在我的情况下它可以在任何地方(即%/Folder3)。

所以我正在寻找一个可以从 PHP 使用的快速解决方案。我是开放的:它可以是一个单独的服务器,一个使用正则表达式文件的 PHP 库,...

【问题讨论】:

  • 这不是一个真正的答案,所以我将它作为评论发布。您可能想查看 PHP 提供的 glob 函数来搜索目录。 php.net/manual/en/function.glob.php 在该页面上的一个 cmets 中,我发现如果您尝试搜索超过 500,000 个文件,您可能会耗尽内存。
  • @Kyle 尽管我的示例建议如此,但这与“真实”文件无关,它只是一个示例
  • 某种类型的缓存解决方案如何缓存特定搜索条件的搜索结果?第一次查找会很慢,但随后的查找速度会快如闪电。
  • @crush 是的,这是个好主意,但我相信这本身还不够,因为要与数据库匹配的字符串变化很大,我预计会有很多请求,所以缓存失效可能会变得非常高(除非缓存非常大)。
  • 对不起大家,我犯了一个错误,我反转数据和字符串以匹配数据(通配符实际上在数据中)。我编辑了这个问题,但我相信它并没有改变很多。

标签: php database datasource string-matching database-performance


【解决方案1】:

如果您运行SELECT folder_col, count(*) FROM your_sample_table group by folder_col,您会得到重复的folder_col 值(即count(*) 大于1)吗?

如果没有,这意味着您可以生成一个可以生成有效 sphinx 索引的 SQL(请参阅http://sphinxsearch.com/)。

【讨论】:

  • 是的,这些值是唯一的。您是否有其他信息,例如是否可以将字符串与通配符匹配(在文档中未找到)?这个特定操作的性能与 MySQL 相比如何?在我看来,Sphinx 似乎并不专门用于这项任务(据我所知)
  • 获得有效索引后,您可以将 sphinxsearch 配置为作为守护进程运行。使用 sphinx php 客户端库,您可以构建对 sphinxsearch 的查询,该查询将返回与您的查询匹配的 mysql 表 id。推荐阅读以了解整个过程:ibm.com/developerworks/library/os-php-sphinxsearch 另请阅读当前文档的第 5 章,特别是 sphinxsearch.com/docs/current.html#extended-syntax
【解决方案2】:

您可能希望使用多核方法在很短的时间内解决该搜索,我建议使用 FPGA 进行搜索和匹配,但这可能是最难的方法,请考虑使用 CUDA 的THIS ARTICLE,您可以在 16 倍的正常时间内进行搜索,在多核 CPU 系统中,您可以使用 posix 或计算机集群来完成这项工作(例如 MPI),您可以调用 Gearman 服务以使用高级算法运行搜索。

【讨论】:

  • 使用 Gearman 和多进程之类的东西不会有很多开销吗?我不想大量查询数据库(如 MySQL 数据库)。
【解决方案3】:

如果是我,我会两次存储关键字段......一次向前和一次反转(参见 mysql 的反向功能)。然后您可以使用 left(main_field) 和 left(reversed_field) 搜索索引。如果字符串的中间和开头有通配符(例如“*Folder1*Folder2)”,它对您没有帮助,但是当您在开头或结尾有一个通配符时,它会帮助您。

例如如果你想搜索 */Folder1 然后搜索 where left(reverse_field, 8) = '1redloF/'; 对于 Folder1/*/FolderX 搜索其中 left(reverse_field, 8) = 'XredloF/' and left(main_field, 8) = 'Folder1/'

【讨论】:

  • 非常有趣的想法。确实这并不能涵盖所有情况,但如果我找不到更好的解决方案,这可能是我的后备解决方案
【解决方案4】:

如果您的字符串表示某种层次结构(如您的示例内容中所示),实际上不是“真实”文件,但您说您愿意接受替代解决方案 - 为什么不考虑类似基于文件的索引?

  • 选择一个新目录,例如myindex
  • 使用字符串键作为myindex 中的位置和文件名,为每个条目创建一个空文件

现在您可以使用glob 查找匹配项 - 由于分层文件结构,全局搜索应该比搜索所有数据库条目要快得多。 如果需要,您可以将结果与您的 MySQL 数据相匹配 - 由于您的 MySQL 索引在键上,此操作将非常快。

但不要忘记更新 MySQL 数据库中 INSERTUPDATEDELETE 上的 myindex 结构。

这个解决方案只会在一个庞大的数据集(但不像@Kyle 提到的那样庞大)上竞争,它的层次结构相当深而不是宽。

编辑 抱歉,只有当通配符在您的搜索词中而不是在存储的字符串本身中时,这才有效。

【讨论】:

    【解决方案5】:

    我不建议对 MySQL 中的大量数据进行文本搜索。您需要一个数据库来存储数据,但就是这样。对于搜索,请使用以下搜索引擎:

    这些服务将允许您在眨眼之间进行各种时髦的文本搜索(包括通配符);-)

    【讨论】:

    • 正如另一个答案所说,Sphinx 无法做到这一点(通配符在数据库中,而不是查询词)。 Solr 也没有,而且从文档中似乎也可以找到 Elastic 搜索 (elasticsearch.org/guide/reference/query-dsl/wildcard-query.html)。此外,elasticsearch 声明通配符的限制与 MySQL 查询相同。
    • 确实是我没有完全理解你的问题,但我的建议仍然是一样的。使用搜索引擎进行文本搜索。唯一的问题是您将如何存储数据。他们最简单的方法是将“*”处的内容分解为多个标记并对其进行搜索。可以定义一些额外的过滤器来强制执行顺序。 Solr 有许多自定义字段类型,我相信您可以找到适合您需求的内容。
    【解决方案6】:

    由于通配符 (*) 在您的数据中而不是在您的查询中,我认为您应该从将数据分解为多个片段开始。您应该创建一个具有如下列的索引表:

    dataGroup INT(11),
    exactString varchar(100),
    wildcardEnd varchar(100),
    wildcardStart varchar(100),
    

    如果你有像“Folder1/Folder2”这样的值,将其存储在“exactString”中,并将主数据表中的值的ID分配给上述索引表中的“dataGroup”。

    如果您有像“Folder1/*”这样的值,请将值“Folder1/”存储到“wildcardEnd”,然后再次将主表中值的 id 分配给上表中的“dataGroup”字段。

    然后您可以使用以下方法在查询中进行匹配:

    indexTable.wildcardEnd = LEFT('Folder1/WhatAmILookingFor/Data', LENGTH(indexTable.wildcardEnd))
    

    这会将搜索字符串 ('Folder1/WhatAmILookingFor/Data') 截断为“Folder1/”,然后将其与 wildcardEnd 字段匹配。我认为 mysql 足够聪明,不会对每一行进行截断,而是从第一个字符开始并将其与每一行匹配(使用 B-Tree 索引)。

    像“*/Folder4”这样的值将进入“wildcardStart”字段,但相反。引用艾略特小姐的话:“值得吗,让我来做吧 我放下我的东西,翻转它并反转它”(http://www.youtube.com/watch?v=Ke1MoSkanS4)。所以在“wildcardStart”中存储一个值“4redloF/”。然后像下面这样的 WHERE 将匹配行:

    indexTable.wildcardStart = LEFT(REVERSE('Folder1/WhatAmILookingFor/Folder4'), LENGTH(indexTable.wildcardStart))
    

    当然,您可以在应用程序逻辑中执行“REVERSE”。

    现在谈谈棘手的部分。像 "*/Fo*4" 这样的东西应该分成两条记录:

    # Record 1
    dataGroup ==> id of "*/Fo*4" in data table
    wildcardStart ==> oF/
    wildcardEnd ==> /Fo
    
    # Record 2
    dataGroup ==> id of "*/Fo*4" in data table
    wildcardStart ==> 4
    

    现在,如果您匹配某些内容,则必须注意数据组的每个索引记录都会返回完整匹配,并且不会发生重叠。这也可以在 SQL 中解决,但超出了这个问题。

    【讨论】:

      【解决方案7】:

      数据库不是进行此类搜索的正确工具。您仍然可以使用数据库(任何数据库和任何结构)来存储字符串,但是您必须编写代码才能在内存中进行所有搜索。从数据库中加载所有字符串(几千个字符串真的没什么大不了的),缓存它们并在它们上运行你的搜索\匹配算法。

      您可能必须自己编写算法代码,因为标准工具对于您想要实现的目标来说太过分了,而且无法保证它们能够完全满足您的需求。

      我将为您的基于通配符的字符串构建一个正则表达式,并在您的输入上运行这些正则表达式。在正确使用正则表达式之前,您可能需要做一些工作,但这将是最快的方法。

      【讨论】:

      • 现在这就是我正在做的事情:我将它们加载到内存中,将字符串转换为正则表达式并为每个条目查找匹配项。 PHP 是单线程的,所以没有进行并行处理。我正在为同样的事情寻找 Javascript 解决方案,但是是多线程的。
      • 我明白了。在这种情况下,PHP 中的多线程是您应该解决的问题。这比使用某种平台(无论是数据库还是其他东西)重写整个东西要容易得多。我不知道 PHP,但我现在知道了,你可以从中调用其他东西。用 Java 或 C# 编写它。 C# 是最好的 imo,因为它在多线程上非常简单,并且具有内置的正则表达式支持。
      【解决方案8】:

      我建议将密钥及其关联的有效负载读入按字母数字顺序排列的二叉树表示。如果您的密钥不是非常“聚集”,那么您可以避免(稍微额外的)开销构建平衡树。您还可以避免任何树维护代码,因为如果我正确理解您的问题,数据将经常更改,重建树而不是添加/删除/更新节点是最简单的。读入树的开销类似于执行初始排序,并且遍历树以搜索您的值是直接的,并且比仅针对一堆字符串运行正则表达式更有效。您甚至可能会在处理它时发现,您在树中的通配符会导致一些快捷方式来修剪搜索空间。快速搜索显示大量资源和 PHP sn-ps 以帮助您入门。

      【讨论】:

        【解决方案9】:

        您是否考虑过使用 MySQL 的正则表达式引擎?试试这样的:

        SELECT * FROM your_table WHERE your_query_string REGEXP pattern_column

        这将返回与您的查询字符串匹配的正则表达式键的行。我希望它比运行查询以提取所有数据并在 PHP 中进行匹配的性能更好。

        更多信息在这里:http://dev.mysql.com/doc/refman/5.1/en/regexp.html

        【讨论】:

        • 是的,我已经考虑过了,但是 MySQL 不能在此类操作上使用索引,因此它会进行完整扫描。但是,我从未考虑将所有表格内容加载到 PHP 中。但我相信同类型的 MongoDB 查询会更高效:它仍然会进行全扫描(不能使用索引),但会并行执行匹配(多线程)。
        • 您能帮助我们了解您的最终目标是什么吗?看起来您具有动态树数据结构和规则功能,可将规则应用于根据模式定位在树中的元素。也许针对数据结构运行所有规则而不是针对所有规则运行数据结构的每个元素会更快?您可以将规则存储在数据结构中,以便仅适用于一个分支的规则仅存在于该分支中。我正在推测这一点,但我会对您可以提供的任何其他信息感兴趣。
        • 好吧,我正在使用模式匹配构建一个 ACL 系统:您声明访问规则(例如 User-1 可以访问 Category-1/Article-*)。然后你可以测试“User-1可以访问Category-1/Article-19吗?”
        猜你喜欢
        • 2013-06-08
        • 1970-01-01
        • 2018-07-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-25
        • 1970-01-01
        • 1970-01-01
        • 2016-04-08
        相关资源
        最近更新 更多