Use two FMDB queues (read / write) in one database
I believe my use case is fairly common, but I couldn't find an authoritative answer for this.
I have a sync engine that runs in the background and writes data to my database. This sync can take a long time (I am using FTS). For this I use FMDatabaseQueue
. When I want to read into the database, I use the same queue for the request.
Now that the synchronization process has already queued a large number of transactions in the queue, the application wants to do a read, it must wait for all write transactions to complete before it can complete the request, since it is a sequential queue. The code might look like this:
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..."];
}];
I want to get the results of a query instantly (even if the sync fails) and not wait for completion UPDATE
.
Is it possible to create two FMDatabaseQueue
s, one for write requests and one for read requests? What happens if a read request starts right in the middle of a write transaction?
The code might look like this:
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..."];
}];
Edit:
Also, what confuses me is the documentation, which says:
It was always okay to make an FMDatabase object in a thread. Just don't use one instance across streams.
So my understanding is that I can create two isntances with the same database , but I just need to store them in my stream.
source to share
No, you don't want the two to FMDatabaseQueue
interact with the same database. The whole purpose FMDatabaseQueue
is to provide a single queue to coordinate database calls from different threads through a common single sequential queue.
I wonder, however, about your code in "very slow process" blocks. If it is not database specific stuff, then it should not be inside calls 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..."];
}];
}];
And while they are queued up and running, the main queue can then make its own calls databaseQueue
, using FMDatabaseQueue
to make sure it is coordinated so that it doesn't happen concurrently with the inTransaction
above:
[databaseQueue inDatabase:^(FMDatabase *db) {
FMResultSet *resultSet = [db executeQuery:@"SELECT name..."];
while (![resultSet next]) {
....
}
}];
Obviously you have to manage your background tasks, but this is fine for your application (I used a parallel operation queue, but you can use sequential queues or send queues or whatever you want). Don't get carried away with the spec backgroundQueue
above as your implementation will change.
The main observation is that you should not use a databaseQueue
"very slow process" to manage tasks or interact with the database. Take anything not database related from calls inTransaction
and inDatabase
. Use your own queues to manage your own non-database code. Always get in and out databaseQueue
as quickly as possible, and let a single common FMDatabaseQueue
coordinate interaction with a database originating from multiple threads.
source to share