使用CoreData时,以下多列索引谓词非常慢 - 26,000条记录需要大约2秒钟。
请注意,这两列都已编入索引,并且我有目的地使用>进行查询。并且< =而不是开始,以使其快速:
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"airportNameUppercase >= %@ AND airportNameUppercase < %@ \
OR cityUppercase >= %@ AND cityUppercase < %@ \
upperText, upperTextIncremented,
upperText, upperTextIncremented];
但是,如果我运行两个单独的fetchRequests,每列一个,然后我合并结果,那么每个fetchRequest只需1-2个百分之一秒,并且合并列表(已排序)大约需要1个/十分之一秒。
这是CoreData如何处理多个索引的错误,还是这个预期的行为?以下是我的完整优化代码,它的运行速度非常快:
NSFetchRequest *fetchRequest = [[[NSFetchRequest alloc] init]autorelease];
[fetchRequest setFetchBatchSize:15];
// looking up a list of Airports
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Airport"
inManagedObjectContext:context];
[fetchRequest setEntity:entity];
// sort by uppercase name
NSSortDescriptor *nameSortDescriptor = [[[NSSortDescriptor alloc]
initWithKey:@"airportNameUppercase"
ascending:YES
selector:@selector(compare:)] autorelease];
NSArray *sortDescriptors = [[[NSArray alloc] initWithObjects:nameSortDescriptor, nil]autorelease];
[fetchRequest setSortDescriptors:sortDescriptors];
// use > and <= to do a prefix search that ignores locale and unicode,
// because it's very fast
NSString *upperText = [text uppercaseString];
unichar c = [upperText characterAtIndex:[text length]-1];
c++;
NSString *modName = [[upperText substringToIndex:[text length]-1]
stringByAppendingString:[NSString stringWithCharacters:&c length:1]];
// for the first fetch, we look up names and codes
// we'll merge these results with the next fetch for city name
// because looking up by name and city at the same time is slow
NSPredicate *predicate = [NSPredicate predicateWithFormat:
@"airportNameUppercase >= %@ AND airportNameUppercase < %@ \
OR iata == %@ \
OR icao == %@",
upperText, modName,
upperText,
upperText,
upperText];
[fetchRequest setPredicate:predicate];
NSArray *nameArray = [context executeFetchRequest:fetchRequest error:nil];
// now that we looked up all airports with names beginning with the prefix
// look up airports with cities beginning with the prefix, so we can merge the lists
predicate = [NSPredicate predicateWithFormat:
@"cityUppercase >= %@ AND cityUppercase < %@",
upperText, modName];
[fetchRequest setPredicate:predicate];
NSArray *cityArray = [context executeFetchRequest:fetchRequest error:nil];
// now we merge the arrays
NSMutableArray *combinedArray = [NSMutableArray arrayWithCapacity:[cityArray count]+[nameArray count]];
int cityIndex = 0;
int nameIndex = 0;
while( cityIndex < [cityArray count]
|| nameIndex < [nameArray count]) {
if (cityIndex >= [cityArray count]) {
[combinedArray addObject:[nameArray objectAtIndex:nameIndex]];
nameIndex++;
} else if (nameIndex >= [nameArray count]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
} else if ([[[cityArray objectAtIndex:cityIndex]airportNameUppercase] isEqualToString:
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
nameIndex++;
} else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] <
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]) {
[combinedArray addObject:[cityArray objectAtIndex:cityIndex]];
cityIndex++;
} else if ([[cityArray objectAtIndex:cityIndex]airportNameUppercase] >
[[nameArray objectAtIndex:nameIndex]airportNameUppercase]) {
[combinedArray addObject:[nameArray objectAtIndex:nameIndex]];
nameIndex++;
}
}
self.airportList = combinedArray;
答案 0 :(得分:7)
CoreData无法创建或使用多列索引。这意味着当您执行与多属性谓词对应的查询时,CoreData只能使用一个索引进行选择。随后它使用索引进行其中一个属性测试,但是SQLite不能使用索引来收集第二个属性的匹配项,因此必须在内存中完成所有操作而不是使用其磁盘上的索引结构。
选择的第二阶段最终会变慢,因为它必须将所有结果从磁盘收集到内存中,然后进行比较并将结果丢弃到内存中。因此,与使用多列索引相比,最终可能会产生更多的I / O.
这就是为什么,如果你在谓词的每一列中取消很多潜在的结果,你会看到你正在做的事情,并且在内存中进行两次单独的提取和合并,你会看到更快的结果。如果你做了一次取得。
要回答您的问题,Apple的这种行为并非出乎意料;它只是一个设计决策的结果,不支持CoreData中的多列索引。但是,如果您希望将来看到该功能,则应在http://radar.apple.com处提交错误请求支持多列索引。
与此同时,如果你真的想在iOS上获得最大数据库性能,你可以考虑直接使用SQLite而不是CoreData。
答案 1 :(得分:1)
如有疑问,您应该提交错误。
目前没有任何API可以指示Core Data创建复合索引。如果存在复合索引,则可以毫无问题地使用它。
非索引列不会完全在内存中处理。它们导致表扫描,这与加载整个文件不一样(好吧,除非你的文件只有1个表)。字符串上的表扫描往往非常慢。
SQLite本身在每个查询使用的索引数量上受到限制。基本上只有1,给予或采取某些情况。
您应该使用此查询的[n]标志对规范化文本执行二进制搜索。 ADC上有一个名为“DerivedProperty”的示例项目。它将展示如何规范化文本,以便您可以使用二进制排序规则,而不是默认的ICU集成,以进行花哨的本地化Unicode识别文本比较。
的核心数据中进行快速字符串搜索的讨论已经有了很长时间