iOS FMDB 中你不知道的那些事儿

关键词:FMDB SQLite 事务 性能

SQL-image.jpeg

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 占用率及耗时

image.png

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 占用率及耗时

image.png

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 占用率及耗时

image.png

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 占用率及耗时

image.png

3. 结论

读取 1 条数据:

  • 无索引读取耗时 0.01 s
  • 索引读取耗时 0.001 s

综合比较使用索引读取效率更高.

原因: 索引(Index)是一种特殊的查找表, 数据库搜索引擎用来加快数据检索. 简单地说, 索引是一个指向表中数据的指针. 一个数据库中的索引与一本书的索引目录是非常相似的. 但是请注意, 索引不是万能的, 有时候增加索引反而会降低查询效率. 索引的价值是帮你快速定位, 若定位的数据有很多, 那么它就失去了它的使用价值.

4. 什么情况下要避免使用索引?

  • 查询结果大时

    • 索引不应该使用在较小的表上.
    • 索引不应该使用在数据重复度大的表上, 比如高于10%.
    • 索引不应该使用在含有大量的 NULL 值的列上.
  • 有频繁操作

    • 索引不应该使用在有频繁的大批量的更新或插入操作的表上.
    • 索引不应该使用在频繁操作的列上.

5. 特殊情况

若一个字段的取值小, 比如性别, 通常是不需要创建索引的. 但是还要考虑数值的分布情况, 若最终查询结果很少, 则可以使用索引.

代码仓库地址

猜你喜欢

转载自juejin.im/post/7075274270472945671