具有BOOL检查的NSFetchRequest谓词是SLOW

时间:2011-07-14 06:29:52

标签: iphone core-data

考虑通过5k +条目商店过滤以下2个谓词:

predicate1 = [NSPredicate predicateWithFormat:@"hidden == NO AND name BEGINSWITH[cd] %@", searchString];
predicate2 = [NSPredicate predicateWithFormat:@"name BEGINSWITH[cd] %@", searchString];

我打开-com.apple.CoreData.SQLDebug查看获取请求时间:

谓词1:0.4728s
谓词2: 0.0867s

我错过了什么吗?两列都已编入索引。为什么添加一个简单的布尔检查会减慢获取请求的速度呢?

编辑:根据要求,输出:

CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZHIDDEN, t0.ZID, t0.ZNAME, t0.ZRANK FROM ZARTISTINDEX t0 WHERE ( t0.ZHIDDEN = ? AND ( NSCoreDataStringSearch( t0.ZNAME, ?, 393, 0) OR  NSCoreDataStringSearch( t0.ZNAME, ?, 393, 0))) ORDER BY t0.ZRANK DESC LIMIT 14

rank列也已编入索引。我需要此请求超过0.5秒的原因是它用于自动完成功能。每当用户更改某个文本字段的值时,就会发出该请求。

编辑2 :添加更多内容相关信息:

- (NSArray*)autocompleteSuggestions:(NSString*)searchString {

    NSFetchRequest *request = [[NSFetchRequest alloc] init];
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"ArtistIndex" inManagedObjectContext:self.indexObjectContext];
    [request setEntity:entity];
    [request setFetchLimit:10];

    NSPredicate *predicate = [NSPredicate predicateWithFormat:@"hidden == NO AND (name BEGINSWITH[cd] %@ OR name BEGINSWITH[cd] %@)", searchString, [NSString stringWithFormat:@"the %@", searchString]];
    [request setPredicate:predicate];

    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"rank" ascending:NO];
    [request setSortDescriptors:[NSArray arrayWithObject:sortDescriptor]];
    [sortDescriptor release];

    NSArray *resultsArray = [self.indexObjectContext executeFetchRequest:request error:nil];
    [request release];

    return resultsArray;
}

ArtistIndex实体具有以下属性:

  • id(int32)
  • name(string,indexed)
  • rank(int32,indexed)
  • hidden(BOOL,indexed)

编辑3 :以下是slow query (predicate1)fast query (predicate2) com.apple.CoreData.SQLDebug设置为3的完整SQL输出。更严格的测试为我带来了以下内容测试时间,这是更好的,但仍然有+ 2倍的差异,并真正在自动完成建议上下文中有所作为。或者现在这是一个合理的获取时间差异?

谓词1:0.3772s
谓词2: 0.1633s

3 个答案:

答案 0 :(得分:2)

对于2列的where子句和仅1的索引,sqlite可能决定在布尔字段上使用索引。这导致快速选择,但它会在内部产生一半的数据集加载;然后它开始做另一个子句,但它需要通过查看每个记录来做到这一点。这就是布尔搜索引起减速的原因。

我认为你应该尝试3种方法。

  1. 除非两列都有一个索引,否则2列上的查询将表现不佳。我不相信Core Data允许您在单个索引中索引多个列,但底层的SQLite数据库可以。因此,只要看看这是否是导致问题的原因,请尝试在两列上的sqlite数据库上手动创建索引。重要的是你的布尔值是索引中的第二列,因为它没有足够的区别(它只有2个值所以它需要过滤的数据集是数据库的一半;这使得索引效率略低)。

  2. 添加一个实用程序列,其中包含名称和隐藏字段的串联,并对其进行索引和搜索。这可能不太有效,因为您匹配子字符串。

  3. 如果hidden = YES的记录数量很小,只需将其从谓词中删除并在之后过滤它们。

答案 1 :(得分:2)

我最终在@pothibo和Ivo Jansch的回答第二种方法上采取了建议并做了以下事情:

  1. 当用户输入字段中的第一个字母时,我会获取所有条目 从那个字母开始(所以基本上调用autocompleteSuggestions:方法,但不设置获取请求 fetchLimit财产)
  2. 每次用户添加或删除字母时,请应用适当的字母 NSPredicate使用filteredArrayUsingPredicate:初始获取的数组。
  3. 这会导致初始请求速度稍慢(尽管仍然<1s),但后续提取闪电般快速。

    这是一种非常聪明的方法来执行自动填充建议,因为新的自动填充建议集始终将成为前一个的子集。谢谢@pothibo!

答案 2 :(得分:1)

CoreData并不是真的在这两个查询之间做了很多。这种减速有两种可能的原因,两者都可以通过在Xcode中的应用程序启动参数上设置-com.apple.CoreData.SQLDebug 3来诊断。

  1. 您的慢速查询遇到两个索引,由于某种原因,这在SQLite中很慢
  2. 您的慢查询的SQL未正确生成
  3. 解决方案:

    1. 更新到iOS 5 Beta 3并在数据模型中使用多列索引来索引隐藏和名称属性
    2. bugreport.apple.com,包含应用程序的数据存储和两个查询的SQL调试日志。