关键词:FMDB SQLite 事务 性能
FMDB 大量数据读写, 如何保证性能? 比如向数据库写入10万条数据
一.写入
iOS 中, FMDB 仅仅是对 SQLite 做了一层封装. 所以此处以 FMDB 的使用为例. 因为 SQLite 在同一时间只允许一次写操作, 所以在多线程中建议使用 FMDatabaseQueue 操作.
1. 利用 FMDatabaseQueue 写入
1.1. 代码
- (void)excuteSqlByQueue:(NSString *)sql {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FMDatabaseQueue *dbQueue = [self FMDatabaseQueue];
__block NSInteger i = 0;
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
while (i++ < 100000) {
[db executeUpdate:@"insert into student (name, age, height, score, date) values (?, ?, ?, ?, ?)",
@"John",
@(arc4random()%(30 -20 + 1) + 20),
@((arc4random()%(17 -15 + 1) + 15)*0.1),
@(arc4random()%(90 -50 + 1) + 50),
[NSDate date]];
}
}];
});
}
复制代码
1.2 CPU 占用率及耗时
2. 利用 Transaction 写入
2.1. 代码
- (void)excuteSqlByQueue:(NSString *)sql {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FMDatabaseQueue *dbQueue = [self FMDatabaseQueue];
__block NSInteger i = 0;
[dbQueue inTransaction:^(FMDatabase * _Nonnull db, BOOL * _Nonnull rollback) {
while (i++ < 100000) {
[db executeUpdate:@"insert into student (name, age, height, score, date) values (?, ?, ?, ?, ?)",
@"John",
@(arc4random()%(30 -20 + 1) + 20),
@((arc4random()%(17 -15 + 1) + 15)*0.1),
@(arc4random()%(90 -50 + 1) + 50),
[NSDate date]];
}
}];
});
}
复制代码
2.2 CPU 占用率及耗时
3. 结论
插入 10 万条数据:
- 队列耗时 32 s, CPU 占用率 63%
- Transaction 耗时 8 s, CPU 占用率 34%
综合比较使用 Transaction
效率更高.
原因
: 默认 SQLite 的数据库插入操作, 若不采用事务的话, 它每次写入, 就会触发一次事务操作; 若采用事务提交, 则会将所有的插入操作统一提交.
二.读取
数据读取可以通过增加索引来提高查询效率.
1. 无索引查询
1.1. 代码
- (void)select {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FMDatabaseQueue *dbQueue = [self FMDatabaseQueue];
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
db.shouldCacheStatements = YES;
FMResultSet *res = [db executeQuery:@"select 'select' as class, id, name, age, height, score from student where score = 100"];
while (res.next) {
NSString *class = [res stringForColumn:@"class"];
NSInteger ID = [res intForColumn:@"id"];
NSString *name = [res stringForColumn:@"name"];
NSInteger age = [res intForColumn:@"age"];
CGFloat height = [res doubleForColumn:@"height"];
NSInteger score = [res intForColumn:@"score"];
NSLog(@"class= %@,ID=%ld, name=%@, age=%ld, height=%f, score=%ld",class, (long)ID, name, (long)age, height, (long)score);
}
}];
});
}
复制代码
1.2 CPU 占用率及耗时
2. 利用索引读取
2.1. 代码
- (void)createIndex {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FMDatabaseQueue *dbQueue = [self FMDatabaseQueue];
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
[db executeUpdate:@"create index score_index on student (score)"];
}];
});
}
- (void)select {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
FMDatabaseQueue *dbQueue = [self FMDatabaseQueue];
[dbQueue inDatabase:^(FMDatabase * _Nonnull db) {
db.shouldCacheStatements = YES;
FMResultSet *res = [db executeQuery:@"select 'insert-index' as class, id, name, age, height, score from student indexed by score_index where score = 100"];
while (res.next) {
NSString *class = [res stringForColumn:@"class"];
NSInteger ID = [res intForColumn:@"id"];
NSString *name = [res stringForColumn:@"name"];
NSInteger age = [res intForColumn:@"age"];
CGFloat height = [res doubleForColumn:@"height"];
NSInteger score = [res intForColumn:@"score"];
NSLog(@"class= %@,ID=%ld, name=%@, age=%ld, height=%f, score=%ld",class, (long)ID, name, (long)age, height, (long)score);
}
}];
});
}
复制代码
2.2 CPU 占用率及耗时
3. 结论
读取 1 条数据:
- 无索引读取耗时 0.01 s
- 索引读取耗时 0.001 s
综合比较使用索引读取
效率更高.
原因
: 索引(Index)是一种特殊的查找表, 数据库搜索引擎用来加快数据检索. 简单地说, 索引是一个指向表中数据的指针. 一个数据库中的索引与一本书的索引目录是非常相似的. 但是请注意, 索引不是万能的, 有时候增加索引反而会降低查询效率. 索引的价值是帮你快速定位, 若定位的数据有很多, 那么它就失去了它的使用价值.
4. 什么情况下要避免使用索引?
-
查询结果大时
- 索引不应该使用在
较小
的表上. - 索引不应该使用在
数据重复度大
的表上, 比如高于10%. - 索引不应该使用在
含有大量的 NULL 值
的列上.
- 索引不应该使用在
-
有频繁操作
- 索引不应该使用在有
频繁的大批量的更新或插入操作
的表上. - 索引不应该使用在
频繁操作
的列上.
- 索引不应该使用在有
5. 特殊情况
若一个字段的取值小, 比如性别, 通常是不需要创建索引的. 但是还要考虑数值的分布情况, 若最终查询结果很少, 则可以使用索引.