You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Core Data批量插入随机失败求助(Error 134030)

问题分析与解决:Core Data批量插入随机失败(NSCocoaErrorDomain Code=134030)

核心原因

  1. 频繁触发上下文保存:每次插入单个对象就调用context.save(),会导致Core Data频繁提交事务,不仅效率低下,还容易引发上下文状态的一致性冲突——循环中前一次save的状态尚未完全同步,后续插入操作就已启动,进而触发134030这类一致性错误。
  2. 线程安全隐患:你通过await获取viewContext,但viewContext是绑定主线程的上下文。如果插入操作在后台线程执行,即便使用performAndWait,频繁的跨线程save操作也会破坏上下文的线程安全,引发状态混乱。
  3. 唯一约束冲突(高频诱因):错误提示提到“同一ID多次报错”,说明SinglePerson模型的id字段大概率设置了唯一约束(Unique),而待插入的groupOfPeople中存在重复ID,插入时触发约束冲突导致save失败。且失败后未清理上下文错误状态,后续所有操作都在已污染的上下文中执行,最终导致全部插入失败。
  4. 上下文状态未清理:单次save失败后,上下文会保留错误对象与异常状态,若未执行回滚或重置,后续save操作都会因上下文处于错误状态而失败。这就是为什么上下文查询显示150条数据,但SQLite实际只有部分数据的原因——失败前的对象已提交到数据库,失败后的仅存在于上下文内存中,并未写入持久化存储。

解决办法

1. 批量插入,统一提交

放弃每次插入都save的逻辑,完成所有对象插入后一次性调用save,减少事务提交次数,避免频繁同步带来的冲突:

// 修改调用逻辑
let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext
var counter = 0
do {
    for person in groupOfPeople {
        let newSinglePerson = SinglePerson(context: context)
        newSinglePerson.id = person.ID
        newSinglePerson.name = person.name
        newSinglePerson.age = person.age
        // ...其他属性赋值
        counter += 1
    }
    // 统一保存所有插入的对象
    try context.save()
    logComment = logComment + "..success!(\(counter))"
} catch let error {
    print(error)
    Logging.insertError(message: "IMPORT ERROR: \(error.localizedDescription)", location: "buildDatabase20")
    // 失败后回滚上下文,清除错误状态
    context.rollback()
}

2. 改用后台上下文处理批量操作

如果插入操作在后台执行,不要使用主线程的viewContext,创建私有后台上下文保证线程安全:

let container = (UIApplication.shared.delegate as! AppDelegate).persistentContainer
let backgroundContext = container.newBackgroundContext()
backgroundContext.perform { [weak self] in
    guard let self = self else { return }
    var counter = 0
    do {
        for person in groupOfPeople {
            let newSinglePerson = SinglePerson(context: backgroundContext)
            newSinglePerson.id = person.ID
            newSinglePerson.name = person.name
            newSinglePerson.age = person.age
            // ...其他属性赋值
            counter += 1
        }
        try backgroundContext.save()
        // 回到主线程更新日志
        DispatchQueue.main.async {
            self.logComment = self.logComment + "..success!(\(counter))"
        }
    } catch let error {
        print(error)
        Logging.insertError(message: "IMPORT ERROR: \(error.localizedDescription)", location: "buildDatabase20")
        backgroundContext.rollback()
    }
}

3. 处理唯一约束冲突

  • 提前过滤重复数据:在插入前对groupOfPeople去重,确保ID唯一:
    // 利用字典去重,保留最后一个出现的对象(可根据需求调整规则)
    let uniquePeople = Dictionary(grouping: groupOfPeople, by: { $0.ID }).map { $0.value.last! }
    
  • 插入前检查存在性:如果无法提前过滤,在插入单个对象前先查询上下文是否已有相同ID的对象:
    func saveSinglePerson(sPerson: singlePerson, context: NSManagedObjectContext) throws {
        let fetchRequest: NSFetchRequest<SinglePerson> = SinglePerson.fetchRequest()
        fetchRequest.predicate = NSPredicate(format: "id == %@", sPerson.ID as CVarArg)
        let count = try context.count(for: fetchRequest)
        if count == 0 {
            let newSinglePerson = SinglePerson(context: context)
            newSinglePerson.id = sPerson.ID
            newSinglePerson.name = sPerson.name
            newSinglePerson.age = sPerson.age
            // ...其他属性赋值
        }
    }
    

4. 错误发生后强制清理上下文状态

无论哪种场景,save失败时必须调用context.rollback()重置上下文状态,避免后续操作受错误状态影响:

do {
    try context.save()
} catch {
    print(error)
    Logging.insertError(message: "IMPORT ERROR: \(error.localizedDescription)", location: "buildDatabase20")
    context.rollback() // 重置上下文,清除错误对象与异常状态
}

5. 移除不必要的await

viewContext是同步获取的主线程上下文,不需要用await,多余的异步获取可能导致线程切换,引发不必要的状态问题。


内容的提问来源于stack exchange,提问作者blendstylez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 10:46:15