批量插入到iphone上的sqlite数据库中

时间:2010-03-15 08:54:47

标签: iphone performance sqlite bulkinsert

我正在插入一批100条记录,每条记录包含一个包含任意长HTML字符串的dictonary,而且上帝说它很慢。在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 个答案:

答案 0 :(得分:4)

您需要注意的是,SQLite文档会警告您远离多个线程访问/写入数据库。只要您从单个线程访问数据库,您就可以了。如果该线程是程序的主线程或其他线程,则无关紧要。

请记住,iPhone上的SQLite编译版本的线程模式设置为“多线程”,根据documentation,“禁用数据库连接和准备好的语句对象的静音。应用程序是负责序列化对数据库连接和预处理语句的访问,但是启用了其他互斥锁,这样只要没有两个线程同时尝试使用相同的数据库连接,SQLite就可以安全地在多线程环境中使用。因此,如果您决定将此事务放在另一个线程上,请注意您尝试对数据库执行的其他操作。

话虽这么说,我首先要按照Yonel的建议切换到“BEGIN”和“COMMIT”。如果这没有帮助,请将事务移动到另一个线程。从我所听到的情况来看,使用“blob”可能会非常缓慢。

答案 1 :(得分:1)

您是否尝试使用"BEGIN""COMMIT"代替"BEGIN EXCLUSIVE TRANSACTION""COMMIT TRANSACTION"代码?

我只是使用BEGIN和COMMIT,它比提交每个事务要快得多,所以我猜它正在使用这些关键字。

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

答案 2 :(得分:0)

我看到很多情况下,新手iPhone的开发人员认为代码很慢,只是要求轻量级硬件进行大量处理。处理数百(数千?)个“任意长的HTML字符串”可能会使iPhone无法及时执行任务。

请记住,iPhone不是一个非常强大的硬件。它使用专用硬件拉出所有漂亮的图形,这些硬件的计算能力无法用于其他任务。即使您优化了代码,它也可能比您根据您对完全成熟的笔记本电脑和台式机的体验所期望的那样慢。

我建议您使用Instruments(或者甚至只使用带有时间戳的NSLog)来分析代码,而不是猜测瓶颈在哪里,以确切地查看代码在大部分时间内花费的确切位置。

答案 3 :(得分:0)

避免阻塞问题的更好方法是使用异步回调。 尝试使用Enorm EGO sqlite包装器https://github.com/jdp-global/egodatabase

查看我的自述文件部分,了解EGODatabaseRequest - 对db的异步请求/插入。

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];
}