Использование 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

Как мое приложение нуждается в безопасности, я не знаю, что делать еще.

Хорошо, надеюсь, это поможет. Лучший

Хами.