核心数据“数据库出现损坏”——导致此错误的原因是啥?

Posted

技术标签:

【中文标题】核心数据“数据库出现损坏”——导致此错误的原因是啥?【英文标题】:Core Data "The Database appears corrupt" -- What causes this error?核心数据“数据库出现损坏”——导致此错误的原因是什么? 【发布时间】:2014-03-30 05:04:18 【问题描述】:

我在这里碰壁了,我正在使用 Core Data 作为 SQLLite DB,并且我能够成功地保存到数据库中(我已经在离线 SQLLite 浏览器中检查了内容),但是保存第一个查询后,我尝试运行返回以下错误,我在 Internet 上找不到与此特定错误相关的任何有用信息:

核心数据:错误:-executeRequest:遇到异常= 数据库出现损坏。 (无效的主键)与 userInfo = NSFilePath = "/Users/user/Library/Application Support/iPhone Simulator/7.0.3/Documents/db.sqlite";

这里的问题是导致此错误的原因,因为我找不到任何相关信息。

对于一些背景知识,这是我的设置,请假设我对所做的设计有充分的理由,并且不提供“更改你的设计”的答案,除非你能看到一些根本上被破坏的东西模式本身。

我有 3 个托管对象上下文,它们都是 NSPrivateQueueConcurrencyType,第一个 (A) 附加到 Persistent Store Coordinator,第二个 (B) 将 A 设置为其父上下文,第三个 (C) 具有 B设置为它的父上下文——一个链。这样做的原因是C是可写上下文,从网络源获取数据并同步保存,B是UI元素共享的上下文,我希望它是响应式的,最后A是设计的后台上下文卸载上下文 B 和 C 中保存到磁盘的任何延迟

PSC

如果我采取最后一步(将 A 保存到 PSC),那么应用程序运行良好,将所有内容保存在内存中并查询内存中的上下文。崩溃仅在我添加保存步骤后发生,并且仅在保存后对数据库运行的第一个查询中发生。我的 Save 和我的 fetch 执行都包含在 performBlock 中:

这是最后的存档:

- (void)deepSave

    // Save to the Save Context which happens in memory, so the actual write to disk operation occurs on background thread
    // Expects to be called with performBlock

    NSError *error = nil;
    [super save:&error];
    NSAssert(!error, error.localizedDescription);

    // Trigger the save context to save to disk, operation will be queued and free up read only context
    NSManagedObjectContext *saveContext = self.parentContext;
    [saveContext performBlock:^
        NSError *error = nil;
        [saveContext save:&error];
        NSAssert(!error, error.localizedDescription);
    ];

这里是执行堆栈(在 NSManagedObjectContext 队列线程上)

#0  0x0079588a in objc_exception_throw ()
#1  0x079d98e7 in -[NSSQLiteConnection handleCorruptedDB:] ()
#2  0x078d9b8d in -[NSSQLiteConnection fetchResultSet:usingFetchPlan:] ()
#3  0x078e24a5 in newFetchedRowsForFetchPlan_MT ()
#4  0x078cd48e in -[NSSQLCore newRowsForFetchPlan:] ()
#5  0x078cca8d in -[NSSQLCore objectsForFetchRequest:inContext:] ()
#6  0x078cc53f in -[NSSQLCore executeRequest:withContext:error:] ()
#7  0x078cbf62 in -[NSPersistentStoreCoordinator executeRequest:withContext:error:] ()
#8  0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#9  0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#10 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#11 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#12 0x013c34b0 in _dispatch_client_callout ()
#13 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#14 0x013b0422 in dispatch_barrier_sync_f ()
#15 0x0791e2a2 in _perform ()
#16 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#17 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()
#18 0x0791e526 in -[NSManagedObjectContext(_NestedContextSupport) _parentObjectsForFetchRequest:inContext:error:] ()
#19 0x0799c1f4 in __82-[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:]_block_invoke ()
#20 0x0791e321 in internalBlockToNSManagedObjectContextPerform ()
#21 0x013c34b0 in _dispatch_client_callout ()
#22 0x013b0778 in _dispatch_barrier_sync_f_invoke ()
#23 0x013b0422 in dispatch_barrier_sync_f ()
#24 0x0791e2a2 in _perform ()
#25 0x0791e14e in -[NSManagedObjectContext(_NestedContextSupport) executeRequest:withContext:error:] ()
#26 0x078c96c6 in -[NSManagedObjectContext executeFetchRequest:error:] ()

【问题讨论】:

这种情况发生的唯一情况是您的数据库中的某一行的 z_pk=0。识别该行并确定它的创建位置将是调试的第一步。 @ImHuntingWabbits -- 我在 SQLLite 浏览器中打开了数据库,在崩溃时只有一个实体表中有数据(我正在运行查询的实体) 并且它具有正常的、顺序的 Z_PK 值,从 1 开始:( 在这种情况下,线程可能是罪魁祸首,但适当使用 performBlock 应该可以解决这个问题。此时,这些行是直接从 SQLite API 读取的,因此当 CoreData 说数据库已损坏时,它几乎不可能撒谎。如果您使用的是 WAL 模式,请尝试将其关闭(切换到删除日志模式)并重复您的测试。如果失败,您应该能够找到有问题的行。 @ImHuntingWabbits 你能看看我找到的解决方案吗? ***.com/a/22108189/285694 有一个根本问题,子上下文不是为线程设计的。 【参考方案1】:

好的,我已经找到了。在此设置中,针对 NSManagedObject resultType(不应该使用,我们的错误)的 propertiesToFetch 似乎存在问题——针对具有父上下文而不是持久协调器的上下文。此单元测试表明,您所要做的就是设置要获取的属性以获取此错误(在没有要获取的属性的情况下进行查询时可以正常工作)。我们的解决方法是停止错误地使用属性来获取:)

- (void)testManagedObjectContextDefect
        
    NSManagedObjectContext *contextA = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    contextA.persistentStoreCoordinator = sqllitePersistentStoreCoordinator;
    NSManagedObjectContext *contextB = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
    contextB.parentContext = contextA;

    NSEntityDescription *entityDescription = [NSEntityDescription entityForName:@"GCSCObject" inManagedObjectContext:contextB];

    [contextB performBlockAndWait:^
        GCSCObject *object = [[GCSCObject alloc] initWithEntity:entityDescription insertIntoManagedObjectContext:contextB];
        object.serverID = @"1";
        NSError *error = nil;
        XCTAssert([contextB save:&error] && !error, @"Failed to save - %@",error); // B -> A save
    ];

    [contextA performBlock:^
        NSError *error = nil;
        XCTAssert([contextA save:&error] && !error, @"Failed to save - %@",error); // A -> PSC, background save
    ];

    [contextB performBlockAndWait:^
        NSFetchRequest *request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
        NSError *error = nil;
        NSArray *results = [contextB executeFetchRequest:request error:&error];
        XCTAssert(results.count == 1 && !error, @"Fetch failed to retrieve - %@ / %@",results,error);
        GCSCObject *object = results[0];
        XCTAssert([object.serverID isEqualToString:@"1"], @"Value retrieval failed");

        // Everything passes up to here, so far so good!

        request = [[NSFetchRequest alloc] initWithEntityName:@"GCSCObject"];
        request.propertiesToFetch = @[@"serverID"]; // This is the culprit of the index crash
        results = [contextB executeFetchRequest:request error:&error];
        XCTAssert(!error, @"%@", error.localizedDescription); // !!! HERE we have a failure, assert: "Core Data: error: -executeRequest: encountered exception = The database appears corrupt.  (invalid primary key) with userInfo =  NSFilePath = "/path/db.sqlite ";
    ];

本例GCSCObject是一个常规实体,serverID是它的参数之一(不管用哪个参数,或者是什么类型,我都试过了。这里是serverID参数的说明我用于此测试:

无论我们是否提供 andWait 到 contextA 保存,都会发生崩溃(尽管这样做会导致使用后台队列进行保存)

我希望得到有关为什么会出现这种情况的反馈,但就目前而言,不使用属性来获取可以让我们的应用程序顺利运行。我正在考虑在这里提交 Apple Bug。

【讨论】:

如果不请求NSDictionaryResultType,就不能使用propertiesToFetch,所以我不确定这在任何情况下如何工作。此外,您应该将NSPropertyDescription 实例传递给propertiesToFetch 而不是NSString 的实例。但是,在正常情况下,这些问题都不会导致您出错。但是,作为下一步,我建议删除它们并确认没有它们问题仍然存在。顺便说一句,我不会将当前状态的代码提交给 Apple Radar,Apple 会告诉你我到目前为止告诉你的同样的事情。 我不认为你在关注我。您说propertiesToFetch 导致崩溃。我告诉你,你用错了。在大多数情况下,您对它的使用会被忽略(经过测试)。在您的具体示例中,您找到了一种使其崩溃的方法。这很可能不是 Apple 错误,而是开发人员错误。如果将结果类型更改为字典,它仍然会崩溃吗? 我仍然会向 Apple 提交错误,但要获得更好的错误。这种类型的 API 滥用应该比损坏的数据库更容易调试。 propertiesToFetch: "如果设置了 NSManagedObjectResultType,则不能使用 NSExpressionDescription,结果是托管对象错误,部分预填充了命名属性",所以看起来你可以使用它。 我在获取托管对象时尝试使用propertiesToFetch 时遇到了这个问题(因此是 NSManagedObjectResultType)。就我而言,很明显:如果我将 propertiesToFetch 设置为某个(有效值),无论它是字符串数组(键)还是 NSPropertyDescriptions(对应于那个键),我都会得到“数据库出现损坏。(无效的主键) )“ 错误。所以看起来即使文档似乎说它应该工作,但实际上它并没有。以防万一,我在 ios 10.1 上得到它。【参考方案2】:

如果您在执行获取某个聚合结果(sum、max、min、...)的 fetch 请求时遇到此错误,请确保您设置

fetchRequest.resultType = NSDictionaryResultType;

【讨论】:

这对我来说是个问题。具体来说,我使用的是BNRCoreDataStack,因为我懒得建立自己的知道它是如何工作的。我正在为NSFetchedResultsController 构建NSFetchRequest,我将请求的propertiesToFetch 设置为resultType,但没有将resultType 设置为NSDictionaryResultType。 github.com/RestKit/RestKit/issues/1642#issuecomment-39413788【参考方案3】:

首先,您的 UI 上下文不应是私有队列上下文。这就是NSMainQueueConcurrencyType 的用途。

其次,保存时不要检查错误。检查从-save: 返回的BOOL。即使成功保存,该错误也可能包含垃圾。

第三,你的另外两个存档是什么样子的?如果它们都在私有队列上并且被异步保存,那么您可能会遇到竞争情况。 C 应该同步保存,B 应该同步保存,然后 A 应该是异步的。

【讨论】:

UI 上下文是一个私有上下文,因为我们试图让主线程尽可能地响应。我们的表视图等正确使用 performBlock -> 执行查询 -> 从 NSManagedObject 中复制结果字符串 -> 将异步调度到主线程并进行更改。更痛苦,但将繁重的 fetch 查询远离主线程。我正在研究您的其他建议,谢谢! 关于你的第二点,我已经改变了代码来捕捉它,但遗憾的是没有新的错误:(最后,关于你的第三点,这是其他两个保存的样子-- pastie.org/8809824 我将 C->B 和 B->A 保存在同步调用中:/ 将 UI 上下文设置为私有不会给您带来性能优势。它会让事情变慢。您对 UI 显示的获取将是异步的,或者您将阻止它们。更糟糕的是,像NSFetchedResultsController expect 这样的东西会有一个主线程NSManagedObjectContext。我怀疑使用像你这样的私有上下文本质上是稳定的。 不要检查断言中的错误。你应该只检查BOOL。除非BOOL 返回NO,否则该错误是没有意义的。 我们需要为 UI 显示异步进行提取 :) 增加了一点延迟,因为您将显示工作推迟到查询完成后的运行循环(而不是相同的运行循环),但是如果您阻止查询,则触摸、幻灯片等都积压在查询本身之后(UI 滞后)。 Fetched Result 控制器委托响应可以分派到主线程以处理表(我们正在这样做)。这种失败的情况根本不涉及 UI 上下文 (B),因为它在保存 C 然后查询 C 的单元测试期间失败了

以上是关于核心数据“数据库出现损坏”——导致此错误的原因是啥?的主要内容,如果未能解决你的问题,请参考以下文章

是啥导致此运行时错误 13“类型不匹配”?

在 Windows 机器上运行 Postgresql 11 时导致“更多无法识别...”错误的原因是啥?

是啥导致 weka 中的 csv 加载错误?

核心动画渲染错误 506 是啥意思?

什么原因导致C中出现分段错误(核心转储)?

是啥导致 OpenSplice 中出现此 Java 错误?