【问题标题】:Is this a bug I should submit to Apple, or is this expected behavior?这是我应该提交给 Apple 的错误,还是这是预期的行为?
【发布时间】:2011-07-03 23:15:19
【问题描述】:

使用 CoreData 时,以下多列索引谓词非常慢 - 26,000 条记录几乎需要 2 秒。

请注意,这两列都已编入索引,我特意使用 > 和

NSPredicate *predicate = [NSPredicate predicateWithFormat:
  @"airportNameUppercase >= %@ AND airportNameUppercase < %@ \
        OR cityUppercase >= %@ AND cityUppercase < %@ \
    upperText, upperTextIncremented,
    upperText, upperTextIncremented];

但是,如果我运行两个单独的 fetchRequest,每列一个,然后合并结果,那么每个 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;

【问题讨论】:

  • 看起来您正试图过早地优化您的请求谓词。您是否尝试过更简单的谓词并发现它们更慢?
  • 顺便说一句,您显然在用 SQL 术语考虑这一点,这将导致 Core Data 的悲痛。虽然 Core Data 经常使用 SQLite 存储,但这是最近添加的,实际上与 Core Data 的核心功能无关,即管理内存中的对象图。

标签: objective-c cocoa-touch core-data


【解决方案1】:

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

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

这就是为什么,如果您在谓词的每一列中取消许多潜在结果的资格,那么通过执行您正在做的事情并进行两次单独的提取并在内存中合并,您会看到比您更快的结果如果你取得了一次,会。

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

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

【讨论】:

  • +1 建议他提交功能请求错误。如有疑问,请提交错误!
  • 从这个解释来看,似乎任何带有 OR 语句的查询使用不同的索引列都必须在内存中处理整个数据库。但在实践中似乎并非如此,因为其中许多语句比处理整个数据库要快。我错过了什么?
  • 他们不必在内存中处理整个数据库。 CoreData 可以使用一个索引根据谓词的一部分取消一堆行的资格。但是在它使用那个索引之后,它必须在内存中处理语句的其余 where 子句。因此,例如,如果您有一个包含名为 is_male 的列的表,并且该表中的一半行代表女性,那么 where 子句中带有“is_male=0 AND age > 30”的查询只需在记住表格内容的一半是女性。
  • 除非他的对象图,即数据复杂度非常低,例如索引卡的单个实体列表,很难通过分块核心数据来获得整体速度。必须手动管理内存中的所有数据通常会破坏数据库读取中获得的任何优势。
  • @TechZen 是的,可能是这样。 I/O 性能与内存处理性能是您通常必须根据应用程序的具体情况达到的平衡,这取决于您存储的数据。没有一种方法总是适用于所有类型的数据。
【解决方案2】:

如有疑问,您应该提交错误。

目前没有任何 API 可以指示 Core Data 创建复合索引。如果存在复合索引,则可以毫无问题地使用它。

非索引列不会完全在内存中处理。它们会导致表扫描,这与加载整个文件不同(好吧,除非您的文件只有 1 个表)。对字符串的表扫描往往很慢。

SQLite 本身在每次查询使用的索引数量方面受到限制。基本上只有1个,给予或接受一些情况。

您应该为此查询使用 [n] 标志来对规范化文本进行二进制搜索。 ADC 上有一个名为“DerivedProperty”的示例项目。它将展示如何规范化文本,以便您可以使用二进制排序规则而不是默认的 ICU 集成来进行花哨的本地化 Unicode 感知文本比较。

https://devforums.apple.com/message/363871https://devforums.apple.com/message/363871 上有关于 Core Data 中快速字符串搜索的更长讨论

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-07-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-07-25
    • 2019-08-11
    • 1970-01-01
    • 2023-03-11
    相关资源
    最近更新 更多