【问题标题】:How to optimize Core Data query for full text search如何为全文搜索优化 Core Data 查询
【发布时间】:2010-12-18 23:08:10
【问题描述】:

在文本中搜索匹配词时,我可以优化 Core Data 查询吗? (这个问题也与 iPhone 上的自定义 SQL 与 Core Data 的智慧有关。)

我正在开发一个新的 (iPhone) 应用程序,它是用于科学数据库的手持参考工具。主界面是一个标准的可搜索表格视图,当用户输入新词时,我希望在输入时做出响应。单词匹配必须是文本中单词的前缀。文本由 100,000 个单词组成。

在我的原型中,我直接编写了 SQL。我创建了一个单独的“单词”表,其中包含主要实体的文本字段中的每个单词。我对单词进行了索引并按照以下方式进行了搜索

SELECT id, * FROM textTable 
  JOIN (SELECT DISTINCT textTableId FROM words 
         WHERE word BETWEEN 'foo' AND 'fooz' ) 
    ON id=textTableId
 LIMIT 50

这运行得非常快。使用 IN 可能同样有效,即

SELECT * FROM textTable
 WHERE id IN (SELECT textTableId FROM words 
               WHERE word BETWEEN 'foo' AND 'fooz' ) 
 LIMIT 50

LIMIT 至关重要,它可以让我快速显示结果。如果达到限制,我会通知用户显示太多。这很笨拙。

过去几天我一直在思考迁移到 Core Data 的优势,但我担心架构、索引和重要查询的查询缺乏控制。

理论上,textField MATCHES '.*\bfoo.*' 的 NSPredicate 可以正常工作,但我确信它会很慢。这种文本搜索似乎很常见,我想知道通常的攻击是什么?你会像我上面那样创建一个单词实体并使用谓词“word BEGINSWITH 'foo'”吗?它会和我的原型一样快吗? Core Data 会自动创建正确的索引吗?我找不到任何明确的方法来建议持久存储有关索引的信息。

我在我的 iPhone 应用程序中看到了 Core Data 的一些不错的优势。错误和其他内存考虑允许对 tableview 查询进行有效的数据库检索,而无需设置任意限制。对象图管理让我无需编写大量 SQL 即可轻松遍历实体。迁移功能将来会很好。另一方面,在资源有限的环境(iPhone)中,我担心自动生成的数据库会因元数据、不必要的反向关系、低效的属性数据类型等而变得臃肿。

我应该潜入还是谨慎行事?

【问题讨论】:

    标签: iphone sql cocoa cocoa-touch core-data


    【解决方案1】:

    我做了一个变通的解决方案。我认为它类似于this post。我将合并源代码添加到我的 Core Data 项目中,然后创建了一个不是托管对象子类的全文搜索类。在 FTS 类 I #import "sqlite3.h"(源文件)而不是 sqlite 框架中。 FTS 类保存到与 Core Data 持久存储不同的 .sqlite 文件中。

    当我导入数据时,Core Data 对象将相关 FTS 对象的 rowid 存储为整数属性。我有一个静态数据集,所以我不担心引用完整性,但维护完整性的代码应该是微不足道的。

    为了执行 FTS,我MATCH 查询 FTS 类,返回一组 rowid。在我的托管对象类中,我使用[NSPredicate predicateWithFormat:@"rowid IN %@", rowids] 查询相应的对象。我避免以这种方式遍历任何多对多关系。

    性能提升非常显着。我的数据集是 142287 行,包括 194MB(核心数据)和 92MB(删除了停用词的 FTS)。根据搜索词的频率,我对不常用词(2000 次点击)则为 0.2 秒。

    我确信我的方法存在无数问题(代码膨胀、可能的命名空间冲突、丢失某些核心数据功能),但它似乎可以正常工作。

    【讨论】:

      【解决方案2】:

      为了跟进这个问题,我发现使用 Core Data 进行查询很慢。我已经在这个问题上挠了好几个小时了。

      在我的问题中的 SQL 示例中,有两个实体:textTable 和 words 其中 words 包含每个单词,它被索引,并且 textTable 和 words 之间存在多对多的关系。我只用 4000 个单词和 360 个 textTable 对象填充了数据库。假设 textTable 与 words 对象的关系称为 searchWords,那么我可以在 textTable 实体上使用谓词,看起来像

      predicate = [NSPredicate predicateWithFormat:@"ANY searchWords.word BEGINSWITH %@", query];
      

      (我可以为多个查询词添加此谓词的连词。)

      在 iPhone 上,此查询需要几秒钟。使用更大的测试集对我的手工编码 SQL 的响应是即时的。

      但这甚至还没有结束。 NSPredicate 存在一些限制,使相当简单的查询变得缓慢而复杂。例如,假设在上面的示例中,您想使用范围按钮进行过滤。假设 words 实体包含所有文本字段中的所有单词,但范围会将其限制为来自特定字段的单词。因此,单词可能具有“来源”属性(例如电子邮件的标题和消息正文)。

      那么,如上例所示,全文自然会忽略源属性,但过滤后的查询会将搜索限制为特定的源值。这个看似简单的更改需要一个 SUBQUERY。例如,这不起作用:

      ANY searchWords.word BEGINSWITH "foo" AND ANY searchWords.source = 3
      

      因为两个表达式的真实实体可能不同。相反,您必须执行以下操作:

      SUBQUERY(searchWords, $x, $x.word BEGINSWITH "foo" AND $x.source = 3).@count > 0
      

      我发现这些子查询比使用“ANY”的谓词慢,也许不足为奇。

      此时我很好奇 Cocoa 程序员如何有效地使用 Core Data 进行全文搜索,因为我对谓词评估的速度和 NSPredicates 的可表达性感到沮丧。我撞墙了。

      【讨论】:

      • 感谢您的链接。从那里我发现可执行参数“-com.apple.CoreData.SQLDebug 1”会将sqlite调试发送到stderr。从那个转储中,我看到了查询。查询实际上没有任何问题,但是因为单词 textTable 关系是多对多关系,所以需要加入一个关系表。因此,查询必须跨 3 个表连接。当我删除逆时查询现在在 iPhone 硬件上运行得更快!唉,新模式在 Word 表中有外键,因此每次出现都会重复单词本身和元数据。浪费空间。
      • 您可能会加快速度,但 Apple 建议保持反向关系以保持数据完整性。 “您通常应该对两个方向的关系进行建模,并适当地指定反向关系。Core Data 使用此信息来确保对象图在发生更改时的一致性(请参阅“操作关系和对象图完整性”)。在这里查看更多信息:developer.apple.com/DOCUMENTATION/Cocoa/Conceptual/CoreData/…
      • stackoverflow.com/questions/2197496/… - 这是一个关于多对多关系和 SQLite 性能的相关线程。强迫在性能和完整性之间进行选择(通过消除反向关系)是不可接受的。我需要两者兼得。这个问题似乎是 SQLite 后端特有的,较小的项目可能只需使用二进制存储即可解决它。
      【解决方案3】:

      潜入。

      这是一种解决方法:

      1. 将您的记录放入 Core Data 持久存储中
      2. 使用NSFetchedResultsController 管理Word 实体上的结果集(Core Data 等效于 SQL“words”表)
      3. 使用UISearchDisplayController 在结果集上实时应用NSPredicate

      一旦通过NSFetchedResultsController 获得结果集,就很容易应用谓词。根据我的经验,它也会响应。例如:

      if ([self.searchBar.text length]) {
          _predicate = [NSPredicate predicateWithFormat:[NSString stringWithFormat:@"(word contains[cd] '%@')", self.searchBar.text]];
          [self.fetchedResultsController.fetchRequest setPredicate:_predicate];
      }
      
      NSError *error;
      if (![self.fetchedResultsController performFetch:&error]) {
          // handle error...
      }
      NSLog(@"filtered results: %@", [self.fetchedResultsController fetchedObjects]);
      

      将即时过滤结果集 [self.fetchedResultsController fetchedObjects],对 word 进行不区分大小写的搜索。

      【讨论】:

      • 感谢您的回复。我现在正在编写命令行工具以将初始 sqlite 数据加载到符合 xcdatamodel 的数据库中。涉及大量劳动力。我会报告我的经验。
      • 跟进您的示例,我认为问题在于获取请求不会在 Word 实体上,而是在 textTable 实体上。 (例如,假设 textTable 包含电子邮件,Word 包含所有电子邮件字段中的所有单词。)我认为这会使事情变得非常复杂,因为 fetchResultsController 必须保存通过谓词过滤的 textTable 实体——而这样的 ANY 或 SUBQUERY 谓词是慢。也许有一种方法可以在“相反”的方向上做到这一点:通过使用单词匹配开始,遵循反向关系,并使 textTable 唯一化。嗯。
      • 如果谓词的第一部分尽可能地减少搜索空间,则谓词的其余部分总体上会执行得更快,并且需要搜索的空间更少。在此处查看核心数据指南的性能部分:developer.apple.com/mac/library/documentation/cocoa/conceptual/…
      【解决方案4】:

      在遇到同样的问题后,我遇到了作者遇到同样问题的一系列帖子,并提出了this solution。他报告说,搜索时间从 6-7 秒提高到 0.13 到 0.05 秒。

      他的 FTS 数据集是 79 个文档(文件大小 175k,3600 个离散标记,10000 个引用)。我还没有尝试过他的解决方案,但我想我会尽快发布。另请参阅他的帖子中的 Part 2 以获取他对问题的文档,并参阅 Part 1 以获取他的数据集文档。

      【讨论】:

      • 这个解决方案的问题是查询和关键字必须完全匹配。对于实时结果,您需要任何关键字前缀来匹配查询。在这种情况下,不可能使用对象而不是谓词中的字符串。
      • 自己尝试实现这个并没有任何改进,可能是因为我使用了 contains[cd]。我放弃并开始使用 sqlite3 fts。彼得,感谢您提供的额外链接。我仅限于一个。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2016-04-18
      • 1970-01-01
      • 2018-02-23
      • 1970-01-01
      • 2021-05-19
      相关资源
      最近更新 更多