经过数小时的调试和分析,我得出的结论是,如果核心数据确定有更好的方法,就不能让核心数据使用索引。
这个blog post 帮助我了解了如何使用SQLite 的EXPLAIN QUERY PLAN 来分析用于执行提取的策略。
基本上,我所做的是:
在 Xcode 中:
- 在我的运行方案中设置
-com.apple.CoreData.SQLDebug 1
- 在我设置持久存储后记录了我的 .sqlite 数据库位置。我这样做是因为每次安装时 iOS 8 模拟器路径都会发生变化。
在终端:
- 打开终端并执行
/usr/bin/sqlite3
- 从 sqlite> 提示符我发出了一个
.open 命令,其中包含我之前登录到调试器控制台的数据库路径。我这样做是因为我有多个持久性商店。
- 在 sqlite> 提示符下,我使用 com.apple.CoreData.SQLDebug 记录到调试器控制台的 SQL 选择语句执行了
EXPLAIN QUERY PLAN。
您也可以向 sqlite3 发出.indices 命令来验证您的索引是否符合预期。
显然,这仅适用于模拟器,因为您无法从终端打开设备上的数据库。
为了让事情更容易在多个数据存储和模拟器位置之间移动,我在设置持久存储后调用此方法:
- (void)examineQuery
{
NSMutableString *result = [NSMutableString string];
[result appendString: @"\n/usr/bin/sqlite3"];
[result appendFormat: @"\n.open '%@'", [self.coreDataStack.databaseURL path]];
[result appendString: @"\nEXPLAIN QUERY PLAN "];
NSLog(@"%@",result);
}
我只是从调试器中复制所有 3 行并将它们粘贴到终端中。然后我在调试器中找到记录的SELECT 语句,将其复制并粘贴到终端中。您需要在完成的EXPLAIN QUERY PLAN 命令的末尾键入;,以便 sqlite3 终止输入并处理命令。
在我的例子中 sqlite3 EXPLAIN QUERY PLAN 给出了这个结果:
SCAN TABLE ZART AS t0 USING INDEX ZART_ZNAME_INDEX
好的,它使用了不同的索引。看看我的 NSFetchRequest,我现在可以假设排序描述符正在确定索引。
NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:kEntityName];
request.sortDescriptors = [NSArray arrayWithObjects:
[NSSortDescriptor sortDescriptorWithKey:@"name" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)],
nil];
我更改了我的代码以保证 xDate 永远不会为 NULL,以确定 OR 子句是否导致核心数据不使用我在 xDate 上的索引。我现在可以在模型中使 xDate 不是可选的并更改了我的谓词:
[NSPredicate predicateWithFormat:@"xDate > %@", [NSDate date]]
sqlite3 EXPLAIN QUERY PLAN 给出了相同的结果:
SCAN TABLE ZART AS t0 USING INDEX ZART_ZNAME_INDEX
我尝试的最后一件事是在 xDate,name 上创建复合索引。再次执行EXPLAIN QUERY PLAN 过程会产生相同的结果。
在我的调查过程中,我遇到了多个对核心数据优化提取的引用,但不能保证索引的使用。我假设那是我撞到的墙。
奇怪的是,即使没有使用 xDate 和 xDate,name 索引,我的提取现在也已降至 0.21 秒。我相信改进来自于从模拟器中删除我的应用程序并进行全新安装。我所有的索引使用测试都是在新应用安装后完成的(在你问之前;)
似乎向我的模型添加索引并没有传播到已安装的数据库。我确实尝试过先迁移到新版本的数据库,但它似乎仍然没有添加新的索引。