这是我应该提交给Apple的错误,还是这种预期的行为?

And*_*son 4 cocoa-touch core-data objective-c

使用CoreData时,以下多列索引谓词非常慢 - 26,000条记录需要大约2秒钟.

请注意,这两列都已编制索引,并且我有目的地使用>和<=而不是beginwith进行查询,以使其快速:

NSPredicate *predicate = [NSPredicate predicateWithFormat:
  @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \
        OR cityUppercase >= %@ AND cityUppercase < %@ \
    upperText, upperTextIncremented,
    upperText, upperTextIncremented];
Run Code Online (Sandbox Code Playgroud)

但是,如果我运行两个单独的fetchRequests,每列一个,然后我合并结果,那么每个fetchRequest只需1-2个百分之一秒,并且合并列表(已排序)大约需要1/10第二.

这是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;
Run Code Online (Sandbox Code Playgroud)

Rya*_*yan 7

CoreData无法创建或使用多列索引.这意味着当您执行与多属性谓词对应的查询时,CoreData只能使用一个索引进行选择.随后它使用索引进行其中一个属性测试,但是SQLite不能使用索引来收集第二个属性的匹配项,因此必须在内存中完成所有操作而不是使用其磁盘上的索引结构.

选择的第二阶段最终变慢,因为它必须将所有结果从磁盘收集到内存中,然后进行比较并将结果丢弃到内存中.因此,与使用多列索引相比,最终可能会产生更多的I/O.

这就是为什么,如果你将在谓词的每一列中取消许多潜在结果的资格,你会看到你正在做的事情,并且你在内存中进行两次单独的提取和合并,你会看到更快的结果.取了一个.

为了回答你的问题,Apple的这种行为并不出乎意料; 它只是一个设计决策的结果,不支持CoreData中的多列索引.但是如果您希望将来看到该功能,您应该在http://radar.apple.com上提交一个错误请求支持多列索引.

与此同时,如果您真的想在iOS上获得最大数据库性能,可以考虑直接使用SQLite而不是CoreData.