有没有办法在 FMDB 中 'prime the pump' 以便它可以更快地运行

Is there a way to 'prime the pump' in FMDB so it's ready to operate quicker

我们有一个 sqlite 数据库,我们的 iOS 应用将图像存储在 blob 列中。 我们使用 FMDB 将 blob 读取为 NSData,然后转换为 UIImage。代码如下所示。

-(UIImage*)getImageWithGuid:(NSString *)guid imageSizeKind:(ImageSizeKind)imageSizeKind
    {
        FMDatabase *db = [self openFMDatabase];
        if (!db) {
            return nil;
        }

        NSData *imageData = nil;
        NSString *query = [NSString stringWithFormat:@"SELECT Image FROM images WHERE Guid = '%@' AND MediaType = %d limit 1", guid, imageSizeKind];

        FMResultSet *rs = [db executeQuery:query];

        if ([rs next])
        {
            imageData = [rs dataForColumn:imagesTable.image];
        }

        [rs close];
        [db close];

        if (!imageData) {
            NSLog(@"Image was not found in database '%@' using sql query '%@'", [self databasePath], query);
        }

        UIImage *image = [UIImage imageWithData:imageData];

        return image;
    }

上面这个方法的调用者接收图像然后调整它的大小。 在上面这个方法的调用者中,我有一些代码来对代码的获取和调整部分进行计时,我在调试控制台中收到了以下输出...


23:31:17.084 在 4.208354 秒内获得图像
23:31:17.086 在 0.001961 秒内调整图像大小
23:31:17.115 在 0.028943 秒内获得图像
23:31:17.117 在 0.001891 秒内调整图像大小
23:31:17.131 在 0.013373 秒内获得图像
23:31:17.133 在 0.002036 秒内调整图像大小
23:31:17.844 在 0.711072 秒内获得图像
23:31:17.846 在 0.001634 秒内调整图像大小
23:31:17.880 在 0.034076 秒内获得图像
23:31:17.882 在 0.001678 秒内调整图像大小
23:31:17.910 在 0.028255 秒内获得图像
23:31:17.912 在 0.001652 秒内调整图像大小
23:31:17.943 在 0.031323 秒内获得图像
23:31:17.945 在 0.001783 秒内调整图像大小
23:31:17.954 在 0.009396 秒内获得图像
23:31:17.956 在 0.001982 秒内调整图像大小
23:31:17.986 在 0.029724 秒内获得图像
23:31:17.988 在 0.001977 秒内调整图像大小
23:31:18.026 在 0.037283 秒内获得图像
23:31:18.027 在 0.001837 秒内调整图像大小
23:31:18.051 在 0.023700 秒内获得图像
23:31:18.053 在 0.001947 秒内调整图像大小
23:31:18.088 在 0.035087 秒内获得图像
23:31:18.090 在 0.001687 秒内调整图像大小
23:31:18.136 在 0.045304 秒内获得图像


请注意,第一张图片的获取时间高达 4.2 秒,而接下来的所有图片只用了百分之一秒。

有没有什么方法可以 "prime the pump" 可以这么说,让 4.2 秒的时间不受影响,并让数据库准备好像处理所有后续图像一样运行。理想情况下,将这 4 秒的延迟隐藏在某个后台线程上会很棒,这样用户就不必在应用程序的其他地方体验它,只需将最初的 4 秒移到其他地方即可。

谢谢。

FMDB(或更准确地说,SQLite)不需要任何 "priming"。我只是运行下面的代码:

CFAbsoluteTime last = CFAbsoluteTimeGetCurrent();

for (NSInteger i = 0; i < 10; i++) {
    FMResultSet *rs = [database executeQuery:@"select image_data from images where guid = ?", @(i)];
    NSAssert(rs, @"select failed: %@", [database lastErrorMessage]);
    if ([rs next]) {
        CFAbsoluteTime current = CFAbsoluteTimeGetCurrent();
        NSData *data = [rs dataForColumnIndex:0];
        NSLog(@"%lu %0.3f", (unsigned long)[data length], current - last);
        last = current;
    }
    [rs close];
}

在 iPhone 6+ 上,它报告:

2016-03-24 21:53:36.107 MyApp[3710:1262147] 1000000 0.010
2016-03-24 21:53:36.112 MyApp[3710:1262147] 1000000 0.004
2016-03-24 21:53:36.115 MyApp[3710:1262147] 1000000 0.004
2016-03-24 21:53:36.123 MyApp[3710:1262147] 1000000 0.008
2016-03-24 21:53:36.131 MyApp[3710:1262147] 1000000 0.008
2016-03-24 21:53:36.138 MyApp[3710:1262147] 1000000 0.007
2016-03-24 21:53:36.146 MyApp[3710:1262147] 1000000 0.007
2016-03-24 21:53:36.153 MyApp[3710:1262147] 1000000 0.007
2016-03-24 21:53:36.161 MyApp[3710:1262147] 1000000 0.007
2016-03-24 21:53:36.168 MyApp[3710:1262147] 1000000 0.007

您可能可以从上面的日志中推断出,这是对从数据库中检索 10 个 1 mb BLOB 的基准测试。

所以,有几个可能的问题:

  • 仔细检查您从哪里开始计时。确保在启动计时器和开始检索图像之间没有其他代码。可能您的代码中有其他东西正在减慢应用程序的速度。

    我可能会建议 运行 Instruments' "Time Profiler" 并准确确认造成 4 秒延迟的原因。或者,稍微复杂一点,您有时可以通过在 "System Trace" 工具中对 "System Calls" 中的 "wait time" 进行排序来识别阻塞调用。但是,最重要的是,确认 SQLite 实际上是 4 秒延迟的来源。

    如果您对 Instruments 不适应,您通常可以用老式的方式识别瓶颈,插入越来越多的计时语句并将其缩小到占您 4 秒的一两行延迟。 (例如,是 executeQuery,还是 step,或者图像大小调整而不是图像检索,或者数据库的第一次打开可能是从包到文档的一些昂贵的复制,等等。)

  • 如果问题确实出在 SQLite 上,我会确保您在 guid 上有一个索引(最好是唯一键)。它不太可能导致此问题(特别是如果您在打开下一行之前关闭数据库),但如果性能延迟确实在 SQLite 中,那可能会有所帮助。

    此外,我注意到您在 SQL 语句中使用了 limit 1。您是否真的有多个具有相同 guid 的条目(通常是唯一的)?也许时差与您为第一个密钥获得的匹配次数有关。

  • 还要确保您没有任何其他进程试图同时在同一个 SQLite 数据库上工作。我假设情况并非如此,正如您所说,您在执行基准测试过程之前允许应用程序稳定下来,但我只是为了完整起见才提到这一点。

  • 根据 Wolverine 的观察,最好将图像存储在文件系统中并且只将文件名存储在数据库中,这绝对是正确的(如果我没记错的话,我上次进行基准测试时是 10从 SQLite 检索图像比从 SQLite 获取文件名但随后直接从文件系统检索图像慢 -20%。过去的经验法则是,如果您的图像是缩略图大小,则将它们存储在 SQLite 中就可以了,但如果它们是兆字节大小(而不是 10 KB),则将它们存储在文件系统中开始产生 material 性能改进。不过,我不认为这可以回答为什么您的第一个电话这么慢的问题,但是如果图像更大,则值得考虑。

    如果您在 Stack Overflow 中搜索 [sqlite] blob performance 或执行类似的 google 搜索,您会看到许多关于在 SQLite 中存储非常大的 BLOB 对象的缺点的讨论。

话虽如此,我确实有一个与性能相关的观察结果:我不会每次都打开和关闭数据库。应用程序启动时打开数据库一次并保持打开状态。这只节省了 10 毫秒,所以这不会解决你的 4 秒延迟问题,但它会更有效一些。

但是,最重要的是,我无法重现您报告的性能延迟。如果您可以单独创建一个小型单机 MCVE,那么我们可以帮助您进行诊断。但我怀疑问题出在您目前与我们分享的代码片段以外的地方。