Массовые вставки в sqlite db на iphone - PullRequest
3 голосов
/ 15 марта 2010

Я вставляю партию из 100 записей, каждая из которых содержит диктонар, содержащий произвольно длинные строки HTML, и, боже, это медленно. На iphone runloop блокируется на несколько секунд во время этой транзакции. Является ли мой единственный способ использовать другой поток? Я уже использую несколько для получения данных с HTTP-серверов, и документация sqlite явно не поддерживает многопоточность с базой данных, даже если она должна быть поточно-ориентированной ... Есть ли что-то, что я делаю крайне неправильно, если исправлено, радикально сократить время, необходимое для завершения всей операции?

    NSString* statement;
    statement = @"BEGIN EXCLUSIVE TRANSACTION";
    sqlite3_stmt *beginStatement;
    if (sqlite3_prepare_v2(database, [statement UTF8String], -1, &beginStatement, NULL) != SQLITE_OK) {
        printf("db error: %s\n", sqlite3_errmsg(database)); 
        return;
    }
    if (sqlite3_step(beginStatement) != SQLITE_DONE) {
        sqlite3_finalize(beginStatement);
        printf("db error: %s\n", sqlite3_errmsg(database)); 
        return;
    }

    NSTimeInterval timestampB = [[NSDate date] timeIntervalSince1970];
    statement = @"INSERT OR REPLACE INTO item (hash, tag, owner, timestamp, dictionary) VALUES (?, ?, ?, ?, ?)";
    sqlite3_stmt *compiledStatement;
    if(sqlite3_prepare_v2(database, [statement UTF8String], -1, &compiledStatement, NULL) == SQLITE_OK)
    {
        for(int i = 0; i < [items count]; i++){
            NSMutableDictionary* item = [items objectAtIndex:i];
            NSString* tag       = [item objectForKey:@"id"];
            NSInteger hash      = [[NSString stringWithFormat:@"%@%@", tag, ownerID] hash];
            NSInteger timestamp = [[item objectForKey:@"updated"] intValue];
            NSData *dictionary  = [NSKeyedArchiver archivedDataWithRootObject:item];

            sqlite3_bind_int(   compiledStatement, 1, hash);
            sqlite3_bind_text(  compiledStatement, 2, [tag UTF8String], -1, SQLITE_TRANSIENT);
            sqlite3_bind_text(  compiledStatement, 3, [ownerID UTF8String], -1, SQLITE_TRANSIENT);
            sqlite3_bind_int(   compiledStatement, 4, timestamp);
            sqlite3_bind_blob(  compiledStatement, 5, [dictionary bytes], [dictionary length], SQLITE_TRANSIENT);

            while(YES){
                NSInteger result = sqlite3_step(compiledStatement);
                if(result == SQLITE_DONE){
                    break;
                }
                else if(result != SQLITE_BUSY){
                    printf("db error: %s\n", sqlite3_errmsg(database)); 
                    break;
                }
            }
            sqlite3_reset(compiledStatement);
        }
        timestampB = [[NSDate date] timeIntervalSince1970] - timestampB;
        NSLog(@"Insert Time Taken: %f",timestampB);

        // COMMIT
        statement = @"COMMIT TRANSACTION";
        sqlite3_stmt *commitStatement;
        if (sqlite3_prepare_v2(database, [statement UTF8String], -1, &commitStatement, NULL) != SQLITE_OK) {
            printf("db error: %s\n", sqlite3_errmsg(database)); 
        }
        if (sqlite3_step(commitStatement) != SQLITE_DONE) {
            printf("db error: %s\n", sqlite3_errmsg(database)); 
        }

        sqlite3_finalize(beginStatement);
        sqlite3_finalize(compiledStatement);
        sqlite3_finalize(commitStatement);

Ответы [ 4 ]

4 голосов
/ 15 марта 2010

Необходимо помнить о том, что документация SQLite предупреждает вас о невозможности доступа к базе данных или записи в нее из нескольких потоков. Пока вы обращаетесь к базе данных из одного потока, все будет в порядке. Не имеет значения, является ли этот поток основным потоком вашей программы или каким-либо другим потоком.

Имейте в виду, что скомпилированная версия SQLite на iPhone имеет режим многопоточности, установленный на «многопоточность», который, согласно документации , «отключает мьютекс при подключении к базе данных и подготовленных объектах операторов. Приложение отвечает за сериализацию доступа к соединениям с базой данных и подготовленным операторам, но другие мьютексы включены, так что SQLite будет безопасно использовать в многопоточной среде, если нет двух потоков, пытающихся одновременно использовать одно и то же соединение с базой данных ». Поэтому, если вы решили поместить эту транзакцию в другой поток, будьте осторожны с тем, что еще вы пытаетесь сделать с базой данных.

При этом я сначала следую совету Йонела и переключаюсь на "НАЧАТЬ" И "СОВЕРШАТЬ". Если это не поможет, переместите транзакцию в другой поток. Судя по тому, что я слышал, работа с «каплями» может быть довольно медленной.

1 голос
/ 15 марта 2010

Вы пробовали то же самое, что и ваш код, но с "BEGIN" и "COMMIT" вместо "BEGIN EXCLUSIVE TRANSACTION" и "COMMIT TRANSACTION"?

Я просто использую BEGIN и COMMIT, и это намного быстрее, чем фиксация для каждой транзакции, поэтому я думаю, что она работает с этими ключевыми словами.

http://www.sqlite.org/lang_transaction.html

0 голосов
/ 11 октября 2011

Лучший способ избежать проблем с блокировкой - использовать асинхронные обратные вызовы. Попробуйте использовать упаковку Enorm EGO sqlite https://github.com/jdp-global/egodatabase

Взгляните на мой раздел Readme для EGODatabaseRequest - асинхронные запросы / вставки в БД.

2) Добавьте методы обратного вызова requestDidSucceed / requestDidFail.

 -(void)requestDidSucceed:(EGODatabaseRequest*)request withResult:(EGODatabaseResult*)result
    idx++
    if ([items count]<idx) [self insertRow];

}

-(void)requestDidFail:(EGODatabaseRequest*)request withError:(NSError*)error{

    NSLog(@"WARNING requestDidFail");
}




-(void)insertRow{
    NSMutableDictionary* item = [items objectAtIndex:idx];
    NSInteger hash      = [[NSString stringWithFormat:@"%@%@", tag, ownerID] hash];
    NSInteger timestamp = [[item objectForKey:@"updated"] intValue];
    NSData *dictionary  = [NSKeyedArchiver archivedDataWithRootObject:item];
    NSString *qry = [NSString stringWithFormat:@"INSERT OR REPLACE INTO item (hash, tag, owner, timestamp, dictionary) VALUES (%@, %@, %@, %@, %@);",NUMBER(hash),[tag UTF8String],[ownerID UTF8String],NUMBER(timestamp),dictionary];

    // be sure to use NSNumbers not NSIntegers
    EGODatabaseRequest* request = [[EGODatabaseRequest alloc] initWithQuery:qry parameters:nil];
    request.delegate = self;
    request.database = appDelegate.database;
    request.requestKind = EGODatabaseUpdateRequest; // use update not select
    [request fire];
    [request release];
}
0 голосов
/ 15 марта 2010

Я вижу много случаев, когда разработчики для iPhone считают, что код работает медленно, когда это просто случай, когда легкое оборудование требует большой обработки. Обработка нескольких сотен (тысяч?) «Произвольно длинных HTML-строк» ​​может усложнить задачу iPhone для своевременного выполнения.

Помните, что iPhone не очень мощный аппарат. Он использует всю изящную графику благодаря специальному оборудованию, вычислительная мощность которого недоступна для других задач. Даже если вы оптимизируете код, он может оказаться намного медленнее, чем вы ожидаете, исходя из своего опыта работы с полноценными ноутбуками и десктопами.

Вместо того, чтобы гадать, где находится узкое место, я предлагаю вам профилировать код с помощью Instruments (или даже просто использовать NSLog с метками времени), чтобы точно определить, где код тратит большую часть своего времени.

...