Использование FMDB для нескольких потоков и двух соединений
Я использую два разных типа подключений fmdb в своем приложении:
FMDatabase для всех запросов READ и
FMDatabaseQueue для всех запросов UPDATE.
Оба обрабатываются одним синглэном, который сохраняет оба типа открытыми все время, пока приложение работает.
Оба, чтение и обновление запросов, используются в разных потоках, поскольку некоторые задачи в моем приложении продвигаются в фоновом режиме; как получение данных с сервера и вставка их в db через FMDatabaseQueue в собственный фоновый поток - при чтении некоторой информации из db через FMDatabase и обновлении ViewController с ней в основном потоке.
Моя проблема в том, что после вставки данных в db через FMDatabaseQueue второе соединение (FMDatabase) не возвращает обновленную информацию, поскольку она не находит их. Но я знаю, что данные были вставлены, поскольку я проверил db с помощью инструмента браузера db + ошибок при вставке. Чтобы этого избежать, мне нужно закрыть соединение с FMDatabase db и снова открыть его, чтобы увидеть изменения, сделанные другим соединением. К сожалению, когда мое приложение запускается, есть много вложений, обновления + читаются, так как множество новых данных загружается с сервера, который необходимо обработать, - поэтому закрытие и открытие db каждый раз, когда было произведено обновление, происходит во многих "занятых базами данных", сообщения.
Я использовал один единственный 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];
}
}];
}
и запрос чтения
-(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.
Ответы
Ответ 1
Лично я бы предложил использовать одиночный FMDatabaseQueue
для обоих потоков и позволить очереди координировать действия над двумя потоками. Это то, для чего оно было создано. Он полностью устраняет проблемы с "загруженными базами данных".
При обновлении производительности при выполнении массового обновления вы используете метод FMDatabase
beginTransaction
перед обновлением и commit
в конце? Или используйте метод inTransaction
. Вставка 10 000 записей без транзакций в моем тесте занимает 36,8 секунды, но с транзакциями требуется 0,25 секунды.
Или, если ваше массовое обновление происходит медленно по необходимости (например, вы загружаете какой-то большой источник данных из веб-службы с использованием какого-либо потокового протокола), вы можете:
-
Сначала загрузите все результаты в память без взаимодействия с базами данных, а затем используйте массовое обновление с транзакциями, как описано в предыдущем абзаце; или
-
Если ваши обновления базы данных обязательно ограничены медленным сетевым подключением, используйте отдельные вызовы inDatabase
, чтобы он не привязывал FMDatabaseQueue
при загрузке данных из вашей веб-службы.
В нижней строке, используя транзакции или разумное использование отдельных вызовов inDatabase
, вы можете свести к минимуму, как долго ваша фоновая операция связывает FMDatabaseQueue
, и вы можете добиться синхронного многопоточного взаимодействия с вашей базой данных без слишком сильно блокирует ваш интерфейс.
Ответ 2
Я потратил много часов, пытаясь сделать то же самое, когда нашел свой пост.
Я не знаю, нашли ли вы решение.
После многих попыток и поисков я должен был сдаться, и теперь я ищу другое решение.
Но я хотел бы поделиться своими выводами.
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 с двумя соединениями:
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
Как мое приложение нуждается в безопасности, я не знаю, что делать еще.
Хорошо, надеюсь, это поможет.
Лучший
Хами.