首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >核心数据:使用关系计数谓词获取的性能较差

核心数据:使用关系计数谓词获取的性能较差
EN

Stack Overflow用户
提问于 2015-11-02 18:50:51
回答 2查看 991关注 0票数 0

我从Core数据中获取了几千个对象,我只想返回那些至少有一个与其相关的对象。

当我使用类似于下面的谓词时,获取对象需要很长的时间。大约5-8秒:

代码语言:javascript
运行
复制
NSPredicate(format: "relationName.@count > 0")

是否有一种更有效的方法来执行此提取,或者应该在对象中缓存该值以进行快速查找(即hasRelatedObjects属性)。

如果缓存是最好的路径,我不认为它是微不足道的。例如,如果我修改我的Tag对象,我可以在willSave中获取关系计数,并将其存储在我的新属性中。但是,如果相关对象将标记添加到关系的自身,则Tag对象永远不会更改,因此不会调用willSave

如何确保您调用myTag.addRelatedObject(obj) (myTag对象被更新)或myObj.addRelatedTag(myTag) (myObj被更新),该值被缓存?

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2015-11-03 09:49:21

首先,让我们来做一些原型化,看看这个fetch正在做什么。我想您使用的是SQLite商店。

我黑了一个快速模型,跟你描述的一样。

我定义了一个实体,它与子实体有一对多的关系,其中子实体有一对一的逆关系。

现在,我在模拟器中进行测试,所以我创建了一个包含10 so实体的数据库。每次创建新实体时,至少为其创建一个子实体的可能性约为2%。每个被随机选择的实体在1到10个子实体之间。

因此,我得到了一个数据库,数据库中有10,000,000个实体对象,以及1,101,223个子实体对象,其中199,788个实体对象的关系中至少有一个子实体。

对于最简单的提取请求(与示例中的请求相同),我们得到以下代码.

代码语言:javascript
运行
复制
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"subentities.@count != 0"];
NSArray *results = [moc executeFetchRequest:fetchRequest error:NULL];

和生成的SQL,以及进行获取所需的时间。

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE (SELECT COUNT(t1.Z_PK) FROM ZSUBENTITY t1
    WHERE (t0.Z_PK = t1.ZENTITY) ) <> ? 
CoreData: annotation: sql connection fetch time: 17.9598s
CoreData: annotation: total fetch execution time: 17.9657s for 199788 rows.

如果您对SQL了解很多,您可以看到查询不是最优的。这两张桌子下面都有太多事情要做。

如果我们简单地为关系的数量添加一个缓存,我们就会得到这个结果(注意表没有在计数时被索引)。

代码语言:javascript
运行
复制
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Entity"];
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"subcount != 0"];
NSArray *results = [moc executeFetchRequest:fetchRequest error:NULL];

然后我们得到这些结果。

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT <> ? 
CoreData: annotation: sql connection fetch time: 1.5795s
CoreData: annotation: total fetch execution time: 1.5838s for 199788 rows.

现在,让我们看看如果我们索引subcount字段会发生什么。

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT <> ? 
CoreData: annotation: sql connection fetch time: 1.5749s
CoreData: annotation: total fetch execution time: 1.5788s for 199788 rows.

嗯。没什么更好的。如果我们稍微改变谓词..。

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT > ? 
CoreData: annotation: sql connection fetch time: 0.7805s
CoreData: annotation: total fetch execution time: 0.7843s for 199788 rows.

那花了一半时间。我不太清楚为什么,因为即使慢路径进行了两个二进制搜索,也没有值小于0的记录。

而且,我希望有一个更好的改进,基于这样一个事实,通过排序索引,它应该能够进行二进制搜索,这应该比完全线性扫描速度的一半要好得多。

不管怎么说,这确实表明它可以变得更快。

为了看看我们的下界是什么,我们可以这样做.

代码语言:javascript
运行
复制
NSFetchRequest *fetchRequest = [NSFetchRequest fetchRequestWithEntityName:@"Test"];
fetchRequest.fetchLimit = 199788;
NSArray *results = [moc executeFetchRequest:fetchRequest error:NULL];

这给出了这些结果,也是我们所能期望的最好的结果,因为它基本上不进行搜索。

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0  LIMIT 199788
CoreData: annotation: sql connection fetch time: 0.1284s
CoreData: annotation: total fetch execution time: 0.1364s for 199788 rows.

现在,如果我们只关心它们是否为空,而不关心实际的计数,则可以将缓存的计数改为布尔值,它始终是0或1。

通过采用这种方法,我们的fetch with谓词

代码语言:javascript
运行
复制
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"subcount > 0"];

收益率

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT > ? 
CoreData: annotation: sql connection fetch time: 0.5312s
CoreData: annotation: total fetch execution time: 0.5351s for 199788 rows.

将谓词更改为

代码语言:javascript
运行
复制
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"subcount != 0"];

收益率

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT <> ? 
CoreData: annotation: sql connection fetch time: 1.5619s
CoreData: annotation: total fetch execution time: 1.5657s for 199788 rows.

而这个

代码语言:javascript
运行
复制
fetchRequest.predicate = [NSPredicate predicateWithFormat:@"subcount == 1"];

收益率

代码语言:javascript
运行
复制
CoreData: sql: SELECT 0, t0.Z_PK, t0.Z_OPT, t0.ZNAME, t0.ZSUBCOUNT
    FROM ZENTITY t0 WHERE  t0.ZSUBCOUNT = ? 
CoreData: annotation: sql connection fetch time: 0.5332s
CoreData: annotation: total fetch execution time: 0.5366s for 199788 rows.

所以,骨头上还有一些肉,但我会让你享受一些乐趣。

好的,既然我们想要缓存这些更改,我们如何实现这一点呢?

嗯,最简单的方法就是提供一个自定义方法,每次关系改变时都会用到它。但是,它要求所有的更改都要经过这个过程,而且始终有一些代码在特殊API之外操纵对象的可能性。

注意到计算值需要更新的一种方法是在对象保存时。您可以覆盖willSave并在那里进行任何必要的更改。您还可以观察到上下文-将保存通知,并在其中执行工作。

对我来说,这种方法的主要问题是“将保存”通知发生在验证和与持久存储合并之前。这两个过程中的任何一个都可能更改数据,并且有一些棘手的合并问题可能会导致问题。

真正确保验证和合并已经成为核心的唯一方法是连接到验证阶段。

不幸的是,苹果文档强烈反对这种方法。不过,我在这个模式上取得了很好的成功。

代码语言:javascript
运行
复制
- (BOOL)validateSubcount:(id*)ioValue error:(NSError**)outError
{
    NSUInteger computedValue = [*ioValue unsignedIntegerValue];
    NSUInteger actualValue = computedValue;

    NSString *key = @"subentities";
    if ([self hasFaultForRelationshipNamed:key]) {
        if (self.changedValues[@"subcount"]) {
            if (has_objectIDsForRelationshipNamed) {
                actualValue = [[self objectIDsForRelationshipNamed:key] count];
            } else {
                actualValue = [[self valueForKey:key] count];
            }
        }
    } else {
        actualValue = [[self valueForKey:key] count];
    }

    if (computedValue != actualValue) {
        *ioValue = @(actualValue);
    }
    return YES;
}

这将在保存时自动调用,并且您可以手动调用它(通过validateValue:forKey:error:),如果您想要更多的“频繁”一致性,而不仅仅是在保存时,您可以从对象-确实更改通知(或其他任何地方)调用它。

关于更改为一关系的问题;核心数据将正确处理逆关系。此外,所有涉及的对象都将得到适当的更改。

具体来说,如果你改变了一个子实体的关系。现在您将有三个更新的对象:子实体本身、过去位于关系另一端的实体和现在处于关系另一端的实体。

票数 5
EN

Stack Overflow用户

发布于 2015-11-02 22:20:18

你当然定义了反向关系,对吗?因此,您的关系上的didSet处理程序也应该被调用,即使它是从另一方更改的。

事实上,我认为willSave也应该被称为。你核实过不是吗?

票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/33484766

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档