【问题标题】:How to speed up updating relationship among tables, after one or both tables are already saved?在一个或两个表已经保存后,如何加快表之间的更新关系?
【发布时间】:2018-10-07 22:02:48
【问题描述】:

问题:更新和保存快,表之间的关系在两个或一个表已经保存后有大量数据。

我有五个表 TvGenres、TvSubgenre、TvProgram、Channels、TvSchedules,它们之间的关系如下图所示

现在的问题是所有数据下载都是根据以前的数据按顺序发生的,与 SQLite 不同,我需要设置它们之间的关系,为此我必须一次又一次地搜索表并设置它们之间的关系,即时间-消耗所以我怎样才能更快地做到这一点

我使用了 2 种不同的方法来解决,但都没有按预期工作

首先让我告诉你,下载是如何工作的

首先,我根据用户语言获取所有频道详细信息 从频道中,我获取下一周的所有时间表(这是很多数据(大约 30k+ )) 从时间表数据中,我获取所有程序数据(这又是很多数据)

方法一,

下载所有数据并创建它们的对象列表,然后在所有下载完成后立即存储它们,但仍然设置它们之间的关系需要时间,最糟糕的是,现在循环发生两次,首先我必须循环创建所有类列出然后再次循环以将它们存储在表格视图中,但仍然不能解决关系耗时问题。

方法 2

一一下载,就像下载频道存储它们然后下载时间表存储它们然后下载节目然后将它们存储在核心数据中这一切都可以,但是现在频道与时间表有关系,时间表与节目有关系并设置存储计划时的关系我还获取与该计划相关的频道,然后设置关系,对于程序和计划也是如此,下面是代码,所以我该如何解决这个问题或者我应该如何下载和存储它变成尽可能快。

仅用于存储计划的代码

func saveScheduleDataToCoreData(withScheduleList scheduleList: [[String : Any]], completionBlock: @escaping (_ programIds: [String]?) -> Void) {
    let start = DispatchTime.now()
    let context = coreDataStack.managedObjectContext

    var progIds = [String]()
    context.performAndWait {
        var scheduleTable: TvSchedule!

        for (index,response) in scheduleList.enumerated() {
            let schedule: TvScheduleInformation = TvScheduleInformation(json: response )
            scheduleTable = TvSchedule(context: context)
            scheduleTable.channelId = schedule.channelId
            scheduleTable.programId = schedule.programId
            scheduleTable.startTime = schedule.startTime
            scheduleTable.endTime = schedule.endTime
            scheduleTable.day = schedule.day
            scheduleTable.languageId = schedule.languageId
            scheduleTable.isReminderSet = false

            //if I comment out the below code then it reduce the time significantly from 5 min to 34.74 s
            let tvChannelRequest: NSFetchRequest<Channels> = Channels.fetchRequest()
            tvChannelRequest.predicate = NSPredicate(format: "channelId == %d", schedule.channelId)
            tvChannelRequest.fetchLimit = 1
            do {
                let channelResult = try context.fetch(tvChannelRequest)
                if channelResult.count == 1 {
                    let channelTable = channelResult[0]
                    scheduleTable.channel = channelTable
                }
            }
            catch {
                print("Error: \(error)")
            }
            progIds.append(String(schedule.programId))
            //storeing after 1000 schedules 
            if index % 1000 == 0 {
                print(index)
                do {
                    try context.save()
                } catch let error as NSError {
                    print("Error saving schdeules object context! \(error)")
                }

            }
        }
    }
    let end = DispatchTime.now()
    let nanoTime = end.uptimeNanoseconds - start.uptimeNanoseconds
    print("Saving \(scheduleList.count) Schedules takes \(nanoTime) nano time")
    coreDataStack.saveContext()
    completionBlock(progIds)
}

还有如何使用自动释放池进行正确的批量保存

PS:我发现的所有与核心数据相关的材料都非常昂贵,成本超过 3k,而且免费提供的信息不多,只是基本的东西,即使苹果文档也没有太多与性能调整和批量更新相关的代码和交接关系。提前感谢您的帮助。

【问题讨论】:

  • 你的谓词中应该有'==',而不是'='
  • 我们是在谈论几 1000 条记录(节目、频道等)还是极少数(例如少于 100 条)您坚持的记录?
  • 大约 7,000 多个频道,10,000 个时间表,大约 500 个频道,全部基于用户选择的语言,如果选择所有语言,则可能不止于此
  • "你的谓词中应该有 '==',而不是 '='" 实际上我虽然相同,但它的工作原理
  • @JoakimDanielson '=' 在谓词中是可以的。见Predicate Format String Syntax

标签: swift loops core-data relationship nspredicate


【解决方案1】:

我以前有过这样的项目。没有一个解决方案可以解决所有问题,但以下是一些很有帮助的事情:

队列和批处理

您似乎尝试一次将其全部插入,然后尝试逐个插入。在我的应用程序中,我发现大约 300 是最好的批量大小,但是您必须对其进行调整以查看在您的应用程序中有效的方法,它可能多达 5000 或少至 100。从 300 开始并调整以查看什么变得更好结果。

您有几个过程正在进行,您提到了下载并保存到数据库,但如果您没有提到更多,我不会感到惊讶。队列(NSOperationsQueue)是一个很棒的工具。您可能认为创建队列会减慢速度,但事实并非如此。当您尝试一次做太多事情时,事情就会变得缓慢。

因此,您有一个队列正在下载信息(我建议限制为 4 个并发请求),另一个队列正在将数据保存到核心数据(将并发限制为 1 以免发生写入冲突)。随着每个下载任务完成,它会将结果放入更易于管理的大小和队列中以写入数据库。如果最后一批比其他批小一点,请不要担心。

每次插入到核心数据中都会创建自己的上下文,自己进行提取、保存然后丢弃对象。不要从其他任何地方访问这些对象会崩溃——核心数据不是线程安全的。也只能使用此队列写入核心数据,否则您将遇到写入冲突。 (有关此设置的更多信息,请参阅NSPersistentContainer concurrency for saving to core data)。

查找地图

现在您尝试插入 300 个实体,每个实体都必须查找或创建相关实体。您可能有一些分散在周围的功能来完成此操作。如果您在不考虑性能的情况下对此进行编程,您将轻松执行 300 甚至 600 个获取请求。相反,您只进行一次提取fetchRequest.predicate = NSPredicate(format: "channelId IN %@", objectIdsIamDealingWithNow)。获取后将数组转换为以 id 为键的字典

  var lookup:[String: TvSchedule] = [:]
  if let results = try? context.fetch(fetchRequest) {
      results.forEach { if let channelId = $0.channelId { lookup[channelId] = $0  } }
  }

一旦你有了这个查找图,就不要丢失它。将它传递给需要它的每个函数。如果您创建对象,然后考虑将它们插入到字典中。在核心数据操作中,这个查找字典是你最好的朋友。不过要小心。此对象包含非线程安全的 managedObjects。您在数据库块的开头创建此对象,并且必须在最后丢弃它。

优先过滤关系而不是获取

您没有任何明确处理此问题的代码,但如果您遇到它,我不会感到惊讶。假设您有一个特定的TvSchedule,并且您想查找日程表中以特定语言显示的所有程序。这样做的自然方法是创建一个类似于“TvSchedule == %@ AND langId == %@”的谓词。但是实际上做mySchedule.programs.filter {%@.langId = myLangId }要快很多

分析和调整

我看到您已经在代码中添加日志以查看需要多长时间,这真的很好。我还建议使用 xCode 的 Profile 工具。这对于查找占用大部分时间的函数非常有用。

【讨论】:

  • 您好,感谢您的回复 :-),我已经尝试了从 500 到 1500 的不同批次大小,它确实提高了速度,但我没有使用自动释放池,不明白如何更新答案我得到的新代码和结果以及更改 mainQueueConcurrencyType 和 privateQueueConcurrencyType.parent 会影响速度和数据,因为我看到的很多数据都丢失了
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-11-06
  • 2023-03-25
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-03-06
相关资源
最近更新 更多