我在我的应用中使用了两种不同类型的 fmdb 连接:
所有 READ 查询的 FMDatabase 和 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 插入的信息。
最佳答案
就个人而言,我建议使用单个 FMDatabaseQueue
对于两个线程,让队列协调两个线程上的操作。这就是它的创建目的。它完全消除了那些“数据库繁忙”的问题。
关于性能更新,如果进行批量更新,您是否使用了 FMDatabase
方法 beginTransaction
更新前和commit
在最后?或者使用 inTransaction
方法。在我的测试中插入 10,000 条没有事务的记录需要 36.8 秒,但有事务则需要 0.25 秒。
或者,如果您的批量更新速度很慢(例如,您正在使用某种流式传输协议(protocol)从 Web 服务下载一些大数据源),您可以:
首先将所有结果加载到内存中,不与数据库交互,然后使用上一段所述的事务批量更新;或
如果您的数据库更新必然受到慢速网络连接的限制,请使用单独的
inDatabase
调用,这样它就不会在下载时占用FMDatabaseQueue
来自您的网络服务的数据。
最重要的是,通过使用事务或明智地使用单独的 inDatabase
调用,您可以最大限度地减少后台操作占用 FMDatabaseQueue
的时间,并且您可以实现与您的数据库的同步多线程交互,而不会过多地阻塞您的 UI。
关于ios - 在多线程和两个连接上使用 FMDB,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/16847194/