13

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

FMDatabase 用于所有 READ 查询,FMDatabaseQueue 用于所有 UPDATE 查询。

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

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

我的问题是,通过 FMDatabaseQueue 将数据插入数据库后,第二个连接(FMDatabase)没有返回更新的信息,因为它没有找到它们。但是我知道数据已插入,因为我已经使用数据库浏览器工具检查了数据库 + 插入时没有发生错误。为避免这种情况,我必须关闭 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 插入的信息。

4

2 回答 2

10

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

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

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

  • 首先将所有结果加载到内存中,不与数据库交互,然后使用前一段所述的带事务的批量更新;或者

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

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

于 2013-05-30T23:33:11.360 回答
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

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

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

哈密​​。

于 2013-09-19T22:29:23.803 回答