提高关系表的

时间:2016-06-22 13:23:18

标签: android database performance android-sqlite database-performance

情境:

我在我的Android应用程序中使用了一个相当大的SQLite数据库(大约20 MB),它包含大约50个表。

这些表中的大多数都是通过外键链接的,而且很多时候,我需要一次从两个或多个表中检索信息。举例说明:

表1

Id  |  Name  |  Attribute1  |  Attribute2  |  ForeignKey

1   |  "Me"  |  SomeValue   |  AnotherVal  |     49
2   |  "A"   |     ...      |     ...      |     50
3   |  "B"   |              |              |     49

表2

Id  |  Attribute3  |  Attribute4  |  Attribute5

49  |   ThirdVal   |  FourthVal   |   FifthVal
50  |     ...      |     ...      |     ...

有时候,有两个以上的方式以这种方式链接在一起。几乎在所有的时间里,列数多于上面列出的列数,通常大约有1000行。

我的目标是将数据库中的一些属性显示为RecyclerView中的项目,但我需要使用这两个表来检索这些属性。

我的方法:

目前,我正在使用android-sqlite-asset-helper库将此数据库(.db扩展名)从assets文件夹复制到应用中。当我记录这次复制的时间时,它在732毫秒内完成,没问题。

但是,当我想使用第一个表中的外键从两个表中检索数据时,需要的时间太长。当我测试它时,花了大约11.47 ,我想加快速度。

我检索数据的方式是我读取第一个表中的每一行,并将其放入一个对象中:

public static ArrayList<FirstItem> retrieveFirstItemList(Context context) {
    Cursor cursor = new DbHelper(context).getReadableDatabase()
            .query(DbHelper.TABLE_NAME, null, null, null, null, null, null);
    ArrayList<FirstItem> arrayList = new ArrayList<>();
    cursor.moveToFirst();
    while (!cursor.isAfterLast()) {
        // I read all the values from each column and put them into variables
        arrayList.add(new FirstItem(id, name, attribute1, attribute2, foreignKey));
        cursor.moveToNext();
    }
    cursor.close();
    return arrayList;
}

FirstItem对象除了用于从外键获取SecondItem对象的另一个方法外,还包含getter方法:

public SecondItem getSecondItem(Context context) {
    Cursor cursor = new SecondDbHelper(context).getReadableDatabase().query(
            SecondDbHelper.TABLE_NAME,
            null,
            SecondDbHelper.COL_ID + "=?",
            new String[] {String.valueOf(mForeignKey)},
            null, null, null);
    cursor.moveToFirst();
    SecondItem secondItem = new SecondItem(mForeignKey, attribute3, attribute4, attribute5);
    cursor.close();
    return secondItem;
}

当我将两个表中的值打印到logcat中时(我现在决定不使用任何UI,测试数据库性能),我使用这样的东西:

for (FirstItem firstItem : DBUtils.retrieveFirstItemList(this)) {
    Log.d("First item id", firstItem.getId());
    Log.d("Second item attr4", firstItem.getSecondItem(this).getAttribute4());
}

我怀疑这个方法有问题,因为它需要在 Table1 中的每一行搜索 Table2 - 我认为它效率低下。

一个想法:

我正在考虑使用另一种方法,但我不知道它是否比我目前的解决方案更好,或者它是否是实现我想要的“正确”方法。我的意思是,我不确定是否有一种方法可以稍微修改我当前的解决方案以显着提高性能。不过,我的想法是提高从数据库中读取数据的速度。

当应用程序第一次加载时,将读取来自SQLite数据库的各个表的数据,然后将其放入应用程序中的一个SQLite数据库中。当应用程序第一次运行并且每次更新数据库中的表时,都会发生此过程。我知道这会导致不同行之间的数据重复,但这是我看到的唯一方法,可以避免我必须搜索多个表来生成项目列表。

// read values from SQLite database and put them in arrays

ContentValues cv = new ContentValues();

// put values into variables

cv.put(COL_ID, id);
...
db.insert(TABLE_NAME, null, values);

由于这个过程也需要很长时间(因为有多行),我有点担心这不是最好的想法,但是我读到了一些Stack Overflow答案中的事务,这会增加写入速度。换句话说,我会适当地使用db.beginTransaction();db.setTransactionSuccessful();db.endTransaction();来提高将数据重写到新的SQLite数据库时的性能。

所以新表看起来像这样:

Id  |  Name  |  Attribute1  |  Attribute2  |  Attribute3  |  Attribute4  | Attribute5

1   |  "Me"  |  SomeValue   |  AnotherVal  |   ThirdVal   |   FourthVal  |  FifthVal
2   |  "A"   |     ...      |     ...      |     ...      |     ...      |     ...
3   |  "B"   |  SomeValue   |  AnotherVal  |   ThirdVal   |   FourthVal  |  FifthVal

这意味着虽然表中会有更多列,但我可以避免在第一个表中为每一行搜索多个表,并且数据也可以更容易访问(用于过滤和类似的事情) 。大多数'加载'将在开始时完成,并希望加快交易方法。

概述:

总而言之,我想加快从具有多个表的SQLite数据库中读取数据,我必须在第一个表的每一行中查看这些表,以便产生所需的结果。这需要很长时间,并且效率低下,但我不确定是否有一种方法可以调整当前方法以大大提高读取速度。我想我应该在首次运行应用程序时“加载”数据,方法是将各个表中的数据重新组织到一个表中。

所以我问,两种方法中哪一种更好(主要是关于性能)?有没有办法可以调整我当前的方法,还是我做错了什么?最后,如果有一种比我已经提到过的两种方法更好的方法,那么它是什么以及我将如何实施呢?

2 个答案:

答案 0 :(得分:2)

你应该尝试一些事情:

  • 优化您的装载。据我了解你当前的方法,它遇到了N + 1查询问题。您必须执行查询以获取第一批数据,然后执行原始结果集的每一行的另一个查询,以便您可以获取相关数据。使用该方法会出现性能问题是正常的。我不认为它是可扩展的,我建议你放弃它。最简单的方法是使用连接而不是多个查询。这被称为急切加载。
  • 在表格中引入适当的索引。如果你正在进行很多连接,你应该考虑加速它们。索引是这里显而易见的选择。通常,主键列默认为索引,但外键不是。这意味着您对每个联接的表执行线性搜索,这很慢。我会尝试在您的外键列(以及连接中使用的所有列)上引入索引。尝试在之前和之后测量联接的性能,看看你是否在那里取得了任何进展。
  • 考虑使用数据库视图。当您必须经常执行连接时,它们非常有用。创建视图时,与每次运行连接相比,您可以获得预编译查询并节省相当多的时间。您可以尝试使用连接和视图执行查询,这将显示您将节省多少时间。这样做的缺点是,将结果集映射到Java对象的层次结构有点困难,但至少根据我的经验,性能提升是值得的。
  • 您可以尝试使用某种延迟加载。除非明确请求,否则推迟加载相关数据。这可能很难实现,我认为它应该是你的最后手段,但它仍然是一个选择。您可以获得创造性并利用动态代理或类似的东西来实际执行加载逻辑。

总而言之,对索引/视图保持智能应该在大多数情况下都能做到。将此与热切/延迟加载相结合,您应该能够达到对您的表现感到满意的程度。

编辑:有关索引,观看次数和Android实施的信息

索引和视图不是同一问题的替代方案。它们有不同的特点和应用。

将索引应用于列时,可以加快搜索这些列的值。您可以将其视为线性搜索与树搜索比较。这加速了连接,因为数据库已经知道哪些行对应于所讨论的外键值。它们对简单的select语句也有好处,不仅仅是使用连接的语句,因为它们也加快了where子句标准的执行速度。然而,他们有一个捕获。索引会加快查询速度,但会降低插入,更新和删除操作的速度(因为索引也必须保持不变)。

视图只是预编译和存储的查询,其结果集可以像普通表一样查询。这里的好处是您不需要每次都编译和验证查询。

你不应该只局限于这两件事中的一件。它们不是相互排斥的,并且在组合时可以为您提供最佳结果。

就Android实施而言,没有太多事情可做。 SQLite支持开箱即用的索引和查询。你唯一要做的就是创造它们。最简单的方法是修改数据库创建脚本以包含CREATE INDEXCREATE VIEW语句。您可以将表的创建与索引的创建结合起来,或者如果需要更新现有的模式,可以稍后手动添加。只需检查SQLite手册以获取相应的语法。

答案 1 :(得分:0)

也许试试这个:https://realm.io/products/java/我从不使用它,我对他们的表现一无所知。它可能是一种让你感兴趣的方式......或者不是;)