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

发布于 2024-08-24 18:36:23 字数 3011 浏览 7 评论 0原文

我正在插入一批 100 条记录,每条记录都包含一个包含任意长 HTML 字符串的字典,天哪,速度太慢了。在 iPhone 上,运行循环在此事务期间会阻塞几秒钟。我唯一的办法是使用另一个线程吗?我已经使用了几个从 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);

I'm inserting a batch of 100 records, each containing a dictonary containing arbitrarily long HTML strings, and by god, it's slow. On the iphone, the runloop is blocking for several seconds during this transaction. Is my only recourse to use another thread? I'm already using several for acquiring data from HTTP servers, and the sqlite documentation explicitly discourages threading with the database, even though it's supposed to be thread-safe... Is there something I'm doing extremely wrong that if fixed, would drastically reduce the time it takes to complete the whole operation?

    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);

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

如若梦似彩虹 2024-08-31 18:36:23

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

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

话虽这么说,我首先会听从 Yonel 的建议,切换到“开始”和“提交”。如果这没有帮助,请将事务移至另一个线程。据我所知,使用“blob”可能会非常慢。

The thing that you need to be aware of is that the SQLite documentation warns you away from accessing/writing to the database from multiple threads. As long as you access the database from a single thread, you'll be fine. It doesn't matter if that thread is your program's main thread or some other thread.

Keep in mind that compiled version of SQLite on the iPhone has its threading mode set to "multi-thread" which, according to the documentation, "disables mutexing on database connection and prepared statement objects. The application is responsible for serializing access to database connections and prepared statements but other mutexes are enabled so that SQLite will be safe to use in a multi-threaded environment as long as no two threads attempt to use the same database connection at the same time." So, if you do decide to put this transaction on another thread, be careful of what else you try to do with the database.

That being said, I'd first follow Yonel's advice and switch to "BEGIN" AND "COMMIT". If that doesn't help, move the transaction to another thread. Working with "blobs" can be pretty slow, from what I've heard.

靑春怀旧 2024-08-31 18:36:23

您是否尝试与您的代码相同,但使用 "BEGIN""COMMIT" 而不是 "BEGIN EXCLUSIVE TRANSACTION""提交交易”

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

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

Did you try the same as your code but with "BEGIN" and "COMMIT" instead of "BEGIN EXCLUSIVE TRANSACTION" and "COMMIT TRANSACTION" ?

I'm simply using BEGIN and COMMIT and it's pretty much faster than committing for each transaction so I guess it's working with those keywords.

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

土豪我们做朋友吧 2024-08-31 18:36:23

我看到很多情况下,刚接触 iPhone 的开发人员认为代码很慢,而实际上只是要求轻量级硬件进行大量处理。处理数百(数千?)“任意长的 HTML 字符串”对于 iPhone 来说可能是一项繁重的任务,无法及时执行。

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

我建议您使用 Instruments(甚至仅使用带有时间戳的 NSLog)来分析代码,而不是猜测瓶颈在哪里,以准确了解代码大部分时间都花在哪里。

I see a lot of cases where developers new to the iPhone believe the code is slow when it is simply a case of asking lightweight hardware to do to much processing. Processing several hundred (thousands?) of "arbitrarily long HTML strings" might to heavy a task for the iPhone to carry out in a timely fashion.

Remember that the iPhone isn't a very powerful piece of hardware. It pulls off all the nifty graphics with dedicated hardware whose computational power you can't access for other task. Even if you optimize the code it maybe way slower than you would intuitively expect based on your experience with full fledged laptops and desktops.

Instead of guessing where the bottleneck is, I suggest that you profile the code with Instruments (or even just using NSLog with timestamps) to see exactly where the code is spending most of its time.

等往事风中吹 2024-08-31 18:36:23

避免阻塞问题的更好方法是使用异步回调。
尝试使用 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];
}

A better approach to avoid blocking problems is to use an asynchronous callbacks.
Try using Enorm EGO sqlite wrapper https://github.com/jdp-global/egodatabase

Have a look at my Readme section for EGODatabaseRequest - asynchronous requests /inserts to db.

2) Add the requestDidSucceed /requestDidFail callback methods.

 -(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];
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文