在同一数据库上使用两个 FMDB 队列(读/写)

Use two FMDB queues (read / write) on the same database

我相信我的用例相当普遍,但我找不到权威的答案。

我有一个在后台运行并将数据写入数据库的同步机制。此同步可能需要很长时间(我使用 FTS)。为此,我使用 FMDatabaseQueue。当我想读取数据库时,我使用相同的队列进行查询。

现在,当同步进程已经将大量事务排入队列时,应用程序想要进行读取,它必须等待所有写入事务完成才能进行查询,因为这个是一个串行队列。代码可能如下所示:

FMDatabaseQueue *queue = [self getDatabaseQueue];

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[queue inDatabase:^(FMDatabase *db) {
    FMResultSet *resultSet = [db executeQuery:@"SELECT name..."];
}];

我想立即获得查询结果(即使未完成同步),而不是等待 UPDATE 完成。

我能否创建两个 FMDatabaseQueue,一个用于写入查询,一个用于读取查询?如果读取查询恰好在写入事务的中间开始,会发生什么情况?

代码可能如下所示:

FMDatabaseQueue *writeQueue = [self getWriteDatabaseQueue];
FMDatabaseQueue *readQueue = [self getReadDatabaseQueue];

[writeQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[writeQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[writeQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
    // Very slow process
    [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
}];

[readQueue inDatabase:^(FMDatabase *db) {
    FMResultSet *resultSet = [db executeQuery:@"SELECT name..."];
}];

编辑:

此外,让我感到困惑的是文档,其中指出:

It has always been OK to make a FMDatabase object per thread. Just don't share a single instance across threads.

所以我从中了解到,我可以使用相同的数据库创建两个实例,但我只需要将其保留在它们自己的线程中。

不,您不想让两个 FMDatabaseQueue 与同一个数据库交互。 FMDatabaseQueue 的全部目的是提供一个队列,用于通过共享的单个串行队列协调来自不同线程的数据库调用。

不过,我想知道 "very slow process" 块中的代码。如果那不是特定于数据库的东西,那么它不应该在 inTransaction and/or inDatabase 调用中。

NSOperationQueue *backgroundQueue = [[NSOperationQueue alloc] init]; 
FMDatabaseQueue *databaseQueue = [FMDatabaseQueue databaseWithPath:path]; 

[backgroundQueue addOperationWithBlock:^{
    // very slow process

    [databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
    }];
}];

[backgroundQueue addOperationWithBlock:^{
    // very slow process

    [databaseQueue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db executeUpdate:@"UPDATE docs SET name = ?", "value..."];
    }];
}];

当这些排队时 运行,主队列可以进行自己的 databaseQueue 调用,使用 FMDatabaseQueue 确保以这样的方式进行协调它不会与上面的inTransaction块同时发生:

[databaseQueue inDatabase:^(FMDatabase *db) {
    FMResultSet *resultSet = [db executeQuery:@"SELECT name..."];
    while (![resultSet next]) {
        ....
    }
}];

显然,您应该管理您的后台任务,但是适合您的应用程序(我使用了并发操作队列,但您可以使用串行队列或调度队列或任何您想要的)。不要拘泥于上述 backgroundQueue 的细节,因为您的实施会有所不同。

关键观察是您不应该使用 databaseQueue 来管理您的 "very slow process" 任务以及数据库交互。从 inTransactioninDatabase 调用中取出任何与数据库无关的内容。使用您自己的队列来管理您自己的非数据库相关代码。始终尽可能快地进出 databaseQueue 并让单个共享 FMDatabaseQueue 协调与多个线程发生的数据库交互。