3

我正在插入一批 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 回答 4

4

您需要注意的是 SQLite 文档警告您不要从多个线程访问/写入数据库。只要您从单个线程访问数据库,就可以了。该线程是您程序的主线程还是其他线程都没有关系。

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

话虽如此,我还是先听从 Yonel 的建议,然后切换到“BEGIN”和“COMMIT”。如果这没有帮助,请将事务移至另一个线程。据我所知,使用“blob”可能会很慢。

于 2010-03-15T11:21:01.523 回答
1

您是否尝试过与您的代码相同但使用"BEGIN"and"COMMIT"而不是"BEGIN EXCLUSIVE TRANSACTION"and的方法"COMMIT TRANSACTION"

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

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

于 2010-03-15T10:15:53.943 回答
0

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

请记住,iPhone 并不是一个非常强大的硬件。它使用专用硬件绘制所有漂亮的图形,其计算能力您无法访问其他任务。即使您优化代码,根据您使用成熟的笔记本电脑和台式机的经验,它也可能比您直观预期的要慢得多。

与其猜测瓶颈在哪里,我建议您使用 Instruments 分析代码(或者甚至只使用带有时间戳的 NSLog)来准确查看代码大部分时间花在哪里。

于 2010-03-15T12:32:53.287 回答
0

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

查看我的 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];
}
于 2011-10-10T23:24:05.667 回答