5

我正在为 iPad 开发一个企业销售应用程序,它使用 Sqlite 作为其内部数据库,最近出现了一种奇怪的行为。

我有一个巨大的表格,里面充满了来自其他几个表格的信息(有点像“物化视图”),它可以包含超过 200 万行,具体取决于用户的设置方式。当用户想要搜索一个项目时,应用程序会在这个具有索引列的巨大表以及用作过滤器和/或元数据的其他列上执行查询。我将在下面发布查询和基本思想。无论如何,这个查询在第四代 iPad 上通常会在 2~3 秒内返回,仅此而已,这很好。每次用户点击一个按钮以将他的数据与我们的服务器同步时,这个表就会被删除、重新创建和填充。

但是,最近在同一张表中的相同查询(根本没有相关更改),随机开始需要 40~50 秒。如果您稍后在同一设备上使用相同的过滤器(甚至更改过滤器!)执行相同的操作,同一张表上的相同查询再次需要 2~3 秒。我没有发现任何导致这种减速的具体情况,该应用程序是当时唯一运行的应用程序。设备不是问题,我们已经在至少 5 台不同的 iPad 上看到了这种情况,一台是 iPad 3,另一台是第四代 iPad。

我不认为这是某种缓存,因为应用程序不缓存任何东西,而且这些时间相当随机。有时他们连续 10 次需要 40 秒,然后突然又开始只需要 2 秒,反之亦然。我唯一清楚的是,这种减速只发生在密集使用后(使用该应用程序工作 1 到 2 天),所以在我随身携带的 iPad 上调试时,我也遇到了导致这种行为的麻烦。

我试过的:

  • 将 Instruments 附加到流程并检查减速期间正在使用哪些资源。该应用程序在整个过程中都会大量使用 iPad 的“磁盘”(闪存)。我现在没有再分析的例子,但我认为 CPU 使用率在 30% 左右。RAM 使用量稳定在 90~100MB,这对于我们的应用来说是正常的。
  • 在数据库上运行 VACCUM;- 在我作为示例的数据库上减少了约 50MB。从 ~600MB 到 ~550MB。
  • 在数据库上运行分析;- 没有看到任何改进
  • 在数据库上运行 REINDEX;- 似乎有点帮助,但它并没有解决问题。
  • 终止进程并重新开始 - 没有任何变化

巨大的表构造如下,并且没有任何外键或其他任何其他约束:

CREATE TABLE FMV_CATALOG(
    UNIQUE_ID TEXT,
    PRODUCT_ID INTEGER,
    <bunch of metadata/filtered columns - total of 20 columns>
);

查找产品的查询是:

SELECT
    PRODUCT_ID
    ,UNIQUE_ID
    <all other required columns, ~20 columns>
FROM
    FMV_CATALOG
WHERE
    UNIQUE_ID = '<some id>_<other id>'
AND PRODUCT_NAME LIKE '%iPhone%'
<和其他可选的,很少使用的过滤器。>

我完全没有想法,所以任何帮助将不胜感激。
谢谢!

更新(更多信息):

我忘记提及的重要信息,Rob让我想起了它。我的数据库连接始终处于打开状态,仅在用户注销时才关闭。当我们保持连接打开时,我们注意到应用程序的所有部分都有巨大的性能,因为我们有数百个在其他情况下执行的小查询(但不是在浏览/搜索产品目录时)。

用于创建索引的查询如下:

在 MV_CATALOG(UNIQUE_ID) 上创建索引 IDX_MV_CATALOG;

此外,即使该列被命名为 UNIQUE_ID,它也不是唯一的。本来应该是,现在重复了N次。我知道这是错误的,我们会尽快更改。

这个“UNIQUE_ID”(不是真正唯一的)是通过连接另外两个表的 ID 来填充的。这样,当用户在我们的目录上搜索时,我们的“物化视图”消除了至少三个连接的需要,这将我们的查询时间从大约 20 秒缩短到了大约 2 秒。

我们不会直接在查询中调用 sqlite3 API,我们围绕它开发了一个包装类,并且我们已经使用它至少 2 年了。这是我们第一次遇到这种情况,但这也是我们第一次处理如此多的数据。

4

1 回答 1

3

几个想法:

  1. 您没有向我们展示在FMV_CATALOG. 如果没有别的,如果UNIQUE_ID顾名思义,如果是唯一的,那么我倾向于用 a 定义表PRIMARY KEY

    CREATE TABLE FMV_CATALOG(
        UNIQUE_ID TEXT PRIMARY KEY,
        PRODUCT_ID INTEGER,
        <bunch of metadata/filtered columns - total of 20 columns>
    );
    
  2. 您应该尝试使用 SQLiteEXPLAIN QUERY PLAN命令查看查询并查看其计划,并确保它正在利用您的索引。照原样执行此操作,然后再次使用PRIMARY KEY(如果仍然不这样做,则在您的WHERE子句中的字段上建立索引),并确保最终查询肯定使用您的索引。

  3. 我不知道为什么,如果你有唯一的 id,为什么你还要查看其他字段。如果添加主键(可能还有其他索引)不能解决问题,我可能会尝试根据唯一 id 检索记录,然后检查代码中其他参数的一致性。我认为您不需要这样做,但这是最坏的情况。

就它为什么会变慢而言,如果没有看到代码,就很难猜测发生了什么(我敢肯定这太复杂了,无法在一个简单的 SO 问题中分享)。我可以想象出奇怪的行为,例如,如果您sqlite3_finalize在其中一个sqlite3_prepare_v2语句之后失败,或者您不小心未能关闭数据库,然后在其他地方再次打开它。我可以想象如果sqlite3调用顺序不完全正确,可能会出现性能问题。使用诸如FMDB之类的东西可以最大限度地减少发生此类问题的可能性(以及简化您的 SQLite 代码)。或者,如果这一步过于激进,请尝试编写自己的宏来调用 SQLite 调用,但还要记录您调用过的事实sqlite3函数,然后翻阅该日志并仔细检查 SQLite 调用的顺序。

我唯一能建议的是您是否可以构建一个可以重现异常行为的简化项目。追踪一个Heisenbug可能会让人恼火:除非你能不断地重现这个错误,否则很难追踪到。

于 2013-09-13T18:00:30.420 回答