【问题标题】:SQLite Optimization for Android applicationAndroid 应用程序的 SQLite 优化
【发布时间】:2011-04-30 05:24:00
【问题描述】:

我们的 Android 应用程序中有大约 7-8 个表,每个表平均有大约 8 列。读取和写入操作都是在数据库上执行的,我正在试验并试图找到提高 DataAccess 层性能的方法。所以,到目前为止,我已经尝试了以下方法:

  1. 在 where 子句中使用位置参数(原因:以便 sqlite 使用相同的执行计划)
  2. 将插入和更新包含在事务中(原因:默认情况下,每个数据库操作都包含在事务中。这样做将消除开销)
  3. 索引:除了在主键和唯一键列上默认创建的索引之外,我没有创建任何显式索引。(原因:索引将缩短查找时间)

我在括号中提到了我的假设;如果我错了,请纠正我。

问题:

  1. 我可以在此列表中添加任何其他内容吗?我在某处读到避免使用 db-journal 可以提高更新性能?这是神话还是事实?如果推荐,如何做到这一点?

  2. SQLite3 中是否允许嵌套事务?它们如何影响性能? 问题是我有一个在循环中运行更新的函数,因此,我将循环封闭在事务块中。有时这个函数是从另一个函数内部的另一个循环调用的。调用函数还将循环包含在事务块中。这种事务嵌套如何影响性能?

  3. 我的查询中的 where 子句使用多个列来构建谓词。这些列可能不一定由主键或唯一列组成。我也应该在这些列上创建索引吗?为这样的表创建多个索引是个好主意吗?

【问题讨论】:

    标签: android optimization sqlite


    【解决方案1】:
    1. 准确确定需要优化的查询。获取典型数据库的副本并使用 REPL 对查询进行计时。在优化时使用它来衡量任何收益。

    2. 使用ANALYZE 可以让SQLite 的查询计划器更高效地工作。

    3. 对于SELECTs 和UPDATEs,索引可以提高,但前提是您创建的索引实际上可以被您需要加速的查询使用。在查询中使用EXPLAIN QUERY PLAN 以查看将使用哪个索引或者查询是否需要全表扫描。对于大型表,全表扫描很糟糕,您可能需要索引。任何给定的查询只会使用一个索引。如果您有多个谓词,那么将使用的索引是预计减少结果集最多的那个(基于ANALYZE)。您可以拥有包含多个列的索引(以帮助使用多个谓词进行查询)。如果您有具有多列的索引,则它们只有在谓词从左到右无间隙地匹配索引时才可用(但最后未使用的列很好)。如果您使用排序谓词(<<=> 等),那么它需要位于索引的最后使用的列中。同时使用WHERE 谓词和ORDER BY 都需要一个索引,而SQLite 只能使用一个,因此这可能是性能受到影响的一点。您拥有的索引越多,INSERTs 的速度就越慢,因此您必须根据自己的情况做出最佳权衡。

    4. 如果您有更复杂的查询,无法使用您可能创建的任何索引,您可以对架构进行反规范化,以使查询更简单且可以回答的方式结构化您的数据使用索引。

    5. 如果您正在执行大量INSERTs,请尝试删除索引并在最后重新创建它们。您需要对此进行基准测试。

    6. SQLite does support nested transactions 使用保存点,但我不确定你会在性能方面获得什么。

    7. 您可以gain lots of speed by compromising on data integrity。如果您可以自己从数据库损坏中恢复,那么这可能对您有用。您可能只能在您执行可以手动恢复的密集操作时执行此操作。

    我不确定您可以从 Android 应用程序中获得多少。 SQLite 文档中有一个 more detailed guide 用于优化 SQLite。

    【讨论】:

    • 感谢您抽出宝贵时间回答这个问题。
    【解决方案2】:

    这里有一段代码可以将EXPLAIN QUERY PLAN 结果从正在运行的 Android 应用程序中获取到 Android logcat 中。我从SQLiteOpenHelper dbHelperSQLiteQueryBuilder qb 开始。

    String sql = qb.buildQuery(projection,selection,selectionArgs,groupBy,having,sortOrder,limit);
    android.util.Log.d("EXPLAIN",sql + "; " + java.util.Arrays.toString(selectionArgs));
    Cursor c = dbHelper.getReadableDatabase().rawQuery("EXPLAIN QUERY PLAN " + sql,selectionArgs);
    if(c.moveToFirst()) {
        do {
            StringBuilder sb = new StringBuilder();
            for(int i = 0; i < c.getColumnCount(); i++) {
                sb.append(c.getColumnName(i)).append(":").append(c.getString(i)).append(", ");
            }
            android.util.Log.d("EXPLAIN",sb.toString());
        } while(c.moveToNext());
    }
    c.close();
    

    我把它放到我的ContentProvider.query() 中,现在我可以确切地看到所有查询是如何执行的。 (在我的情况下,问题似乎是查询太多,而不是索引使用不当;但也许这会对其他人有所帮助......)

    【讨论】:

      【解决方案3】:

      我会添加这些:

      1. 在某些情况下,使用 rawQuery() 而不是使用 ContentValues 构建会加快速度。当然,编写原始查询有点乏味。

      2. 如果您有大量字符串/文本类型的数据,请考虑使用全文搜索 (FTS3) 创建虚拟表,这样可以运行更快的查询。您可以在 google 中搜索确切的速度改进。

      【讨论】:

        【解决方案4】:

        在 Robie 的全面回答中补充一点:SQLite 中的 VFS(主要与锁定有关)可以换成替代品。您可能会发现 unix-exclunix-none 之类的替代方案之一更快,但请注意SQLite VFS page上的警告!

        Normalization(表结构)也值得考虑(如果您还没有考虑的话),因为它倾向于提供数据库中数据的最小表示;这是一种权衡,更少的 I/O 换取更多的 CPU,而且在中型企业数据库(我最熟悉的那种)中通常是值得的,但恐怕我不知道是否权衡取舍在 Android 等小型平台上效果很好。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2011-09-13
          • 2013-02-08
          • 1970-01-01
          • 2016-12-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多