在多个线程和两个连接上使用FMDB

时间:2013-05-30 22:56:43

标签: ios multithreading fmdb

我在我的应用中使用了两种不同类型的fmdb连接:

FMDatabase用于所有READ查询和 所有UPDATE查询的FMDatabaseQueue。

两者都由一个单例处理,它在应用程序运行时保持两种类型都打开。

读取和更新查询都在不同的线程中使用,因为我的应用程序中的某些任务是在后台进行的;比如从服务器获取数据并通过FMDatabaseQueue在自己的后台线程中将其插入数据库 - 同时通过FMDatabase从db读取一些信息,并在主线程上用它更新ViewController。

我的问题是,在通过FMDatabaseQueue将数据插入数据库后,第二个连接(FMDatabase)不会返回更新的信息,因为它找不到它们。但我知道插入的数据是因为我使用db浏览器工具检查了数据库+插入时没有发生错误。为避免这种情况,我必须关闭FMDatabase数据库连接并重新打开它以查看其他连接所做的更改。不幸的是,当我的应用程序启动时,有许多插入,更新+读取,因为从服务器加载了许多需要处理的新数据 - 因此每次更新时关闭并打开数据库会发生在许多“数据库忙”消息。

我已经为所有线程使用了一个FMDatabaseQueue并且之前执行(读取,更新)但是当使用带有__block变量的读取查询来从回调中获取结果集而另一个线程执行一些插入(在50-之间)时它非常慢单次交易100次。

最重要的是,数据库是通过sqlcipher加密的 - 不确定它是否重要但是想要提及它。所以每次我必须关闭并打开数据库时,我正在做一个setKey。

我的问题:是否可以在多个线程上使用具有两种不同连接类型的设置,如果是,是否必须关闭并打开FMDatabase连接?或者这个用例有更好的解决方案吗?

更新

执行插入/更新的代码类似于

-(void) create:(NSArray *)transactions
{
    NSMutableString *sqlQuery = [[NSMutableString alloc] initWithString:STANDARD_INSERT_QUERY];

    [sqlQuery appendString:@"(transaction_id, name, date) VALUES (?,?,?)"];

    FMDBDataSource *ds = [FMDBDataSource sharedManager];
    FMDatabaseQueue *queue = [ds getFMDBQ];
    [queue inTransaction:^(FMDatabase *db, BOOL *rollback) {
        [db setKey:[ds getKey]]; // returns the key to decrypt the database
        for (Transaction *transaction in transactions)
        {
            [db executeUpdate:sqlQuery, transaction.transactionId, transaction.name, transaction.date];
        }
    }];
}

和阅读查询

-(Transaction *)read:(NSString *)transactionId
{
    NSString *sqlQuery = [[NSString alloc] initWithString:STANDARD_SELECT_QUERY];
    Transaction *transaction = nil;

    FMDBDataSource *ds = [FMDBDataSource sharedManager];
    FMResultSet *rs = [[ds getFMDB] executeQuery:sqlQuery];

    while ([rs next]) {
        transaction = [[Transaction alloc] init];
        [transaction setTransactionId:[rs stringForColumn:@"transaction_id"]];
        [transaction setName:[rs stringForColumn:@"name"]];
    }

[rs close];
return transaction;
}

FMDBDataSource是一个包含FMDatabase和FMDatabaseQueue连接的单例

- (FMDatabaseQueue *)getFMDBQ
{
    if (self.fmdbq == nil)
    {
        self.fmdbq = [FMDatabaseQueue databaseQueueWithPath:[self getDBPath]];
    }

    return self.fmdbq;
}

- (FMDatabase *) getFMDB
{
    if(self.fmdb == nil)
    {
        self.fmdb = [FMDatabase databaseWithPath:[self getDBPath]];
        [self openAndKeyDatabase]; // opens the db and sets the key as the db is encrypted
    }
    return self.fmdb;
}

正如我所说,使用此代码时,FMDatabase连接无法获取通过FMDatabaseQueue插入的信息。

2 个答案:

答案 0 :(得分:9)

就个人而言,我建议对两个线程使用单FMDatabaseQueue,让队列协调两个线程上的操作。这就是它的创造。它完全消除了那些“数据库繁忙”的问题。

在性能更新中,如果进行批量更新,您是在更新前使用FMDatabase方法beginTransaction还是在结束时使用commit?或者使用inTransaction方法。在我的测试中插入没有事务的10,000条记录需要36.8秒,但是事务需要0.25秒。

或者,如果您的批量更新速度很慢(例如,您使用某种流协议从Web服务下载了一些大数据源),您可以:

  • 首先将所有结果加载到内存中,不进行数据库交互,然后使用上一段中所述的批量更新和事务处理;或

  • 如果数据库更新必然受到慢速网络连接的限制,请使用单独的inDatabase调用,以便在从Web服务下载数据时不会占用FMDatabaseQueue

通过使用事务或明智地使用单独的inDatabase调用,您可以最大限度地缩短后台操作占用FMDatabaseQueue的时间,并且可以实现同步的多线程交互使用您的数据库而不会过多地阻止您的UI。

答案 1 :(得分:3)

当我找到你的帖子时,我花了很多时间试图做同样的事情。 我不知道你是否已找到解决方案。 经过大量的尝试和搜索,我不得不放弃,现在我正在寻找另一种解决方案。 但是,我想分享我的结论。

SQLite具有定义READONLY或READWRITE模式的功能。 FMDB实现类似openWithFlags。

[db openWithFlags:SQLITE_OPEN_READONLY|SQLITE_OPEN_NOMUTEX];

即使我们设置了这些标志,设置这些标志也不允许在写入时读取。 我可以完成读取+写入(不同连接)设置我的数据库以使用WAL journal_mode(http://www.sqlite.org/wal.html)。

但是,SQLCipher螺丝拧紧了。

结论READING + WRITING有2个连接:

FMDB + openWithFlags = BE SAD AND ANGRY
FMDB + openWithFlags + WAL jornal_mode = BE HAPPY
FMDB + SQLCipher + openWithFlags = BE SAD AND ANGRY
FMDB + SQLCipher + openWithFlags + WAL jornal_mode = BE SAD AND ANGRY

由于我的应用程序需要安全性,我不知道该怎么做。

嗯,我希望它有所帮助。 最好的

哈密。