在 iPhone 上将多行插入 sqlite db 的最快方法

Posted

技术标签:

【中文标题】在 iPhone 上将多行插入 sqlite db 的最快方法【英文标题】:Fastest way to insert many rows into sqlite db on iPhone 【发布时间】:2012-08-02 19:06:51 【问题描述】:

谁能解释使用 FMDB 在 iPhone 上插入大量数据的最佳方法是什么?我看到了使用 beginTransaction 命令之类的东西。老实说,我不确定 this 或 setShouldCacheStatements 是做什么的。我遵循了我的同事到目前为止所做的代码,这就是它的样子:

BOOL oldshouldcachestatements = _db.shouldCacheStatements;
[_db setShouldCacheStatements:YES];
[_db beginTransaction];
NSString *insertQuery = [[NSString alloc] initWithFormat:@"INSERT INTO %@ values(null, ?, ?, ?, ?, ?, ?, ?);", tableName];
[tableName release];
BOOL success;

bPtr += 2;
int *iPtr = (int *)bPtr;
int numRecords = *iPtr++;

for (NSInteger record = 0; record < numRecords; record++) 
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    // Some business logic to read the binary stream  

    NSNumber *freq = aFreq > 0 ? [NSNumber numberWithDouble:100 * aFreq / 32768]: [NSNumber numberWithDouble:-1.0];

    // these fields were calculated in the business logic section
    success = [_db executeUpdate:insertQuery,
               cID,                                                                                                   
               [NSNumber numberWithInt:position],                                                                       
               [NSString stringWithFormat:@"%@%@", [self stringForTypeA:typeA], [self stringForTypeB:typeB]],     // methods are switch statements that look up the decimal number and return a string
               [NSString stringWithFormat:@"r%i", rID],                                                               
               [self stringForOriginal:original],                                                                    
               [self stringForModified:modified],                                                                    
               freq];

    [pool drain];

[outerPool drain];

[_db commit];
[_db setShouldCacheStatements:oldshouldcachestatements];

这是我能做到的最快速度吗?写是sqlite的限制吗?我看到了这个:http://www.sqlite.org/faq.html#q19 并且不确定这个实现是否是 fmdb 的最佳实现,或者我是否可以做任何其他事情。其他一些同事提到了一些关于批量插入和优化的事情,但我不确定这意味着什么,因为这是我第一次遇到 sqlite。有什么想法或方向我可以研究吗?谢谢!

【问题讨论】:

【参考方案1】:

首先,在大多数情况下,如果你没有完全错误地使用 sqlite3,你不必担心它的性能。

以下因素提高了INSERT 语句的性能:

交易

正如您已经提到的,交易是最重要的功能。特别是如果您有大量查询,事务将将您的INSERTs 加速约 10 倍

PRAGMA 配置

Sqlite3 提供了几种机制,可以在最坏的情况下避免数据库损坏。在某些情况下,这不是必需的。在其他情况下,这是绝对必要的。以下 sqlite3 命令可能会加快您的 INSERT 语句。您的应用程序正常崩溃不会损坏数据库,但操作系统崩溃可能会。

PRAGMA synchronous=OFF -- may cause corruption if the OS fails
PRAGMA journal_mode=MEMORY -- Insert statements will be written to the disk at the end of the transaction
PRAGMA cache_size = 4000 -- If your SELECTs are really big, you may need to increase the cache
PRAGMA temp_store=MEMORY -- Attention: increases RAM use

停用指标

任何 SQL 索引都会减慢 INSERT 语句的速度。检查您的表是否有一些索引:

.indices <table_name>

如果是,则在事务后删除INDEXCREATE

单选

当您生成新数据时,我看不到使用 BULK 插入的方法。但是,您可以收集数据并只执行一条INSERT 语句。这可能会显着提高您的性能,但也会增加失败的可能性(例如语法)。 一个 hack 遇到另一个 hack:由于 sqlite3 不直接支持这一点,您必须使用 UNION 命令相应地收集所有插入语句。

INSERT INTO 'tablename'
      SELECT 'data1' AS 'column1', 'data2' AS 'column2'
UNION SELECT 'data3', 'data4'
UNION SELECT 'data5', 'data6'
UNION SELECT 'data7', 'data8'

语句缓存

我建议避免使用语句缓存,因为 unfixed issue 具有此功能(据我所知,它不会显着影响性能)。

内存管理

我想提的最后一点是关于 ObjC。与基本操作相比,内存管理需要非常非常多的时间。也许你可以通过在循环外准备这些变量来避免一些stringWithFormat:numberWithDouble:

总结

总而言之,我认为如果你简单地使用transactions,你不会对sqlite3的速度有问题。

【讨论】:

非常彻底的答案,我不明白为什么OP现在还没有接受它。 V.好答案,交易很好地增强了交易。只是我有一个担心:我是否必须更改 SQLite 的 PRAGMA 或者最好不要混淆这个低级选项? @Saeed 很可能没有必要,但这实际上取决于您的用例(即是否需要高内存消耗)。我已经学会了不用担心 sqlite 的速度,只要您使用事务并且您没有完全“滥用”sqlite。但是,如果您要更改参数,请务必阅读documentation 并进行充分测试。 我知道这是一个古老的话题,但我不能强调交易会产生多大的不同。我正在向 ios sqlite 数据库中插入约 10,300 行。没有事务大约需要 7 秒,事务括号内只有 0.8 秒。 @Andrew 我完全同意你的看法。我添加了一个句子来强调重要性。【参考方案2】:

我发现很难找到一个关于如何快速插入多行的具体代码示例。在对 FMDB 进行了大量实验并得到上述答案的帮助之后,这就是我目前正在使用的:

[_objectController.dbQueue inDatabase:^(FMDatabase *db)

    [db open];
    [db setShouldCacheStatements:YES];

    NSArray *documentsJSONStream = responseObject[@"details"][@"stream"];

    static NSString *insertSQLStatment = @"INSERT INTO documents (`isShared`,`thumbnailURLString`,`userID`,`version`,`timestamp`,`tags`,`title`,`UDID`,`username`,`documentURLString`,`shareURLString`) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)";
    [db beginTransaction];
    for (NSDictionary *dJ in documentsJSONStream)
    
        [db executeUpdate:insertSQLStatment withArgumentsInArray:@[dJ[@"isShared"],dJ[@"thumbnailURLString"],dJ[@"userID"],dJ[@"version"],dJ[@"timestamp"],dJ[@"tags"],dJ[@"title"],dJ[@"UDID"],dJ[@"username"],dJ[@"documentURLString"],dJ[@"shareURLString"]]];
    
    [db commit];
    [db close];
 ];

使用 inTransaction 给了我奇怪的 FMDB is not open 错误。如果有任何关于如何改进的建议,请告诉我。

【讨论】:

以上是关于在 iPhone 上将多行插入 sqlite db 的最快方法的主要内容,如果未能解决你的问题,请参考以下文章

使用内爆在sqlite中插入多行[重复]

在多行插入语句 sqlite 上使用触发器

将 sqlite3 db 关联到 iPhone 应用程序

在 nsthread 上运行的 iPhone Sqlite 插入

将图像读取和写入 SQLite DB 以供 iPhone 使用

在 iPhone/iPod Touch 上本地存储包含 333,000 行的 sqlite DB 是不是可行?