适用于Android应用程序的SQLite优化

时间:2010-10-25 13:31:26

标签: android optimization sqlite

我们的Android应用程序中大约有7-8个表,每个表平均有大约8列。读取和写入操作都在数据库上执行,我正在尝试并尝试找到增强DataAccess层性能的方法。所以,我尝试了以下几点:

  1. 在where子句中使用位置参数(原因:以便sqlite使用相同的执行计划)
  2. 附加插入并使用事务更新(原因:默认情况下,每个数据库操作都包含在事务中。执行此操作将消除此开销)
  3. 索引:除了在主键和唯一键列上默认创建的索引之外,我没有创建任何显式索引。(原因:索引将改善搜索时间)
  4. 我已经提到了我在paranthesis中的假设;如果我错了,请纠正我。

    问题:

    1. 我可以在此列表中添加其他内容吗?我在某地读过,避免使用db-journal可以提高更新的性能?这是神话还是事实?如果推荐的话怎么办呢?

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

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

4 个答案:

答案 0 :(得分:12)

  1. 确切地确定需要优化的查询。获取典型数据库的副本并使用REPL计时查询。在优化时使用它来对任何收益进行基准测试。

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

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

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

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

  6. SQLite does support nested transactions使用保存点,但我不确定您是否会在性能方面获得任何收益。

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

  8. 我不确定您可以通过Android应用程序获得多少内容。在SQLite文档中,一般有more detailed guide用于优化SQLite。

答案 1 :(得分:8)

这是从运行的Android应用程序中获取EXPLAIN QUERY PLAN结果到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()中,现在我可以确切地看到所有查询是如何执行的。 (在我的情况下,问题似乎是太多的查询而不是使用索引;但这可能会帮助其他人......)

答案 2 :(得分:1)

我想补充一下:

  1. 在某些情况下,使用rawQuery()而不是使用ContentValues进行构建。当然,编写原始查询有点单调乏味。

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

答案 3 :(得分:0)

一个小问题,可以添加到Robie的全面答案中:SQLite中的VFS(主要与锁定有关)可以换成替代品。您可能会找到其中一个替代方案,例如 unix-excl unix-none 更快,但要注意SQLite VFS page上的警告!

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