我有一个(足球)游戏数据库,包含句号的子表(例如第一和第二半),事件(例如目标,警告)和位置(你在比赛之前和比赛期间)。
要显示父游戏表,我使用带有适当参数的CursorLoader,如下所示:
public Loader<Cursor> onCreateLoader(final int id, final Bundle args) {
...
if ((mGamesDB.isOpen()) && (id == GAMES_CURSOR_ID)) {
return createGamesCursorLoader();
}
return null;
}
private Loader<Cursor> createGamesCursorLoader() {
//Because we don't want to create a ContentProvider for now, we use the technique suggested here:
//https://stackoverflow.com/questions/18326954/how-to-read-an-sqlite-db-in-android-with-a-cursorloader
return new CursorLoader(getBaseContext(),null, GamesContract.Games.PROJECTION,
null, null, GamesContract.Games.ORDER_BY) {
@Override
public Cursor loadInBackground() {
if (mGamesDB.isOpen()) {
return mGamesDB.query(
GamesContract.Games.TABLE_NAME,
GamesContract.Games.PROJECTION,
null, null,
null, null,
GamesContract.Games.ORDER_BY
);
}
else return null;
}
};
}
一切正常。但是,一旦我开始迭代游戏光标(当调用onLoadFinished时),我需要使用当前的GameID为Periods,Events和Locations创建子查询。所以我这样做:
private Game buildGameFromDB(final Cursor gameCursor) {
if (!mGamesDB.isOpen() || (gameCursor == null) || gameCursor.isClosed() ) return null;
final WatchGame game = new WatchGame(gameCursor.getString(GamesContract.Games.COLUMN_ID_INDEX),
gameCursor.getLong(GamesContract.Games.COLUMN_ACTUAL_START_MILLIS_INDEX),
gameCursor.getLong(GamesContract.Games.COLUMN_ACTUAL_END_MILLIS_INDEX),
gameCursor.getInt(GamesContract.Games.COLUMN_HOME_TEAM_COLOR_INDEX),
gameCursor.getInt(GamesContract.Games.COLUMN_AWAY_TEAM_COLOR_INDEX),
gameCursor.getInt(GamesContract.Games.COLUMN_HOME_TEAM_SCORE_INDEX),
gameCursor.getInt(GamesContract.Games.COLUMN_AWAY_TEAM_SCORE_INDEX));
//FIXME: Ugly nested queries on the main UI thread
final String[] periodsWhereArgs = {game.getmGameID()};
final Cursor periodsCursor = mGamesDB.query(GamesContract.Periods.TABLE_NAME, GamesContract.Periods.PROJECTION,
GamesContract.Periods.WHERE, periodsWhereArgs,
null, null, GamesContract.Periods.ORDER_BY);
while (periodsCursor.moveToNext()) {
final Period period = new Period(
periodsCursor.getInt(GamesContract.Periods.COLUMN_PERIOD_NUM_INDEX),
periodsCursor.getLong(GamesContract.Periods.COLUMN_ACTUAL_START_MILLIS_INDEX),
periodsCursor.getLong(GamesContract.Periods.COLUMN_ACTUAL_END_MILLIS_INDEX),
periodsCursor.getFloat(GamesContract.Periods.COLUMN_START_BATTERY_PCT_INDEX),
periodsCursor.getFloat(GamesContract.Periods.COLUMN_END_BATTERY_PCT_INDEX),
periodsCursor.getString(GamesContract.Periods.COLUMN_GOOGLE_ACCOUNT_NAME_INDEX),
periodsCursor.getInt(GamesContract.Periods.COLUMN_NUM_LOCATIONS_INDEX),
periodsCursor.getInt(GamesContract.Periods.COLUMN_NUM_LOCATIONS_IN_FIT_INDEX),
periodsCursor.getInt(GamesContract.Periods.COLUMN_CALORIES_INDEX),
periodsCursor.getInt(GamesContract.Periods.COLUMN_STEPS_INDEX),
periodsCursor.getInt(GamesContract.Periods.COLUMN_DISTANCE_METRES_INDEX),
periodsCursor.getLong(GamesContract.Periods.COLUMN_WALKING_MILLIS_INDEX),
periodsCursor.getLong(GamesContract.Periods.COLUMN_RUNNING_MILLIS_INDEX),
periodsCursor.getLong(GamesContract.Periods.COLUMN_SPRINTING_MILLIS_INDEX)
);
game.addPeriod(period);
}
periodsCursor.close();
...
虽然游戏和时段的数量不会很大(可能是100秒),但每场比赛可能有50场比赛,每场比赛有2000场比赛。
如何更有效地完成这项工作?我遇到的可能性是:
任何建议或指示赞赏。
答案 0 :(得分:1)
您认为自己正在运行N+1 SELECT problem,因为您的应用程序和数据库服务器之间的所有额外通信都会执行许多查询,从而降低性能。
实际上,情况并非如此:SQLite是一个嵌入式数据库,因此没有单独的服务器和many small queries are just as efficient。
但是,您还可以采取其他措施加快查询速度:
添加适当的索引: 用于查找行的列应编入索引;有关详细信息,请参阅Query Planning。 PRIMARY KEY或UNIQUE约束自动在其列上创建索引;对于其他列,您必须自己create the index(es)。
在这种情况下,句号表中的游戏ID应该有一个索引。
加载较少的数据,即仅在实际需要时加载数据。 最简单的方法是删除游戏/句点对象,并直接从数据库运行UI。这将需要更改整个应用程序的体系结构,如果您的对象实际上执行的处理多于仅存储数据,则可能无法实现。
请注意,无论您使用什么类型的查询(N + 1或批量或加入),上述两点都有效。
将数据库访问移动到单独的线程不会加速它们,它只允许用户在数据仍在加载时与UI交互。 (没有所有数据,UI是否有用是另一个问题。)同样,异步线程可以用于任何类型的查询。