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

ach*_*sen 12 multithreading fmdb ios

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

所有READ查询的FMDatabase和所有UPDATE查询的FMDatabaseQueue.

两者都由一个单例处理,这使得两种类型在应用程序运行时保持打开状态.

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

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

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

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

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

UPDATE

我执行插入/更新的代码看起来像

-(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];
        }
    }];
}
Run Code Online (Sandbox Code Playgroud)

和一个读取查询

-(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;
}
Run Code Online (Sandbox Code Playgroud)

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;
}
Run Code Online (Sandbox Code Playgroud)

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

Rob*_*Rob 9

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

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

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

  • 首先将所有结果加载到内存中,不进行数据库交互,然后将批量更新与事务一起使用,如上一段所述; 要么

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

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

  • Rob是对的,最好使用单个FMDatabaseQueue.适合我的作品.我的问题是我在某些情况下删除并创建了一个新数据库,而没有关闭旧连接并重新连接到新创建的数据库.SQLite或FMDB没有提供错误消息或其他东西 - 这就是找到错误并不容易的原因.谢谢 (2认同)