LoginSignup
23
24

More than 5 years have passed since last update.

AndroidでSQLiteのパフォーマンス比較(SELECT編)

Posted at

INSERT編DELETE編に続き、SELECT編。

前提

環境

Nexus5, Android 4.4.4

テーブル

こんなかんじ。

カラム名 制約
id TEXT PRIMARY KEY
name TEXT NOT NULL
address TEXT
count INTEGER NOT NULL
flag INTEGER NOT NULL

INSERT済みのデータ

以下のようなデータ10000件。

id name address count flag
id0 name0 NULL 0 0
id1 name1 NULL 1 0
id2 name2 NULL 2 0
... ... ... ... ...
id9999 name9999 NULL 9999 0

SQLiteDatebase#query() vs SQLiteDatabase#rawQuery

SQLiteStatementはSELECTに対応していないため、この2つのメソッドの比較。

SQLiteDatabase#query()を使った処理
Cursor cursor = db.query("table_name", null, "id=?", new String[]{"id5000"}, null, null, null, null);
try {
    if (cursor.moveToNext()) {
        Hoge hoge = new Hoge();
        hoge.id = cursor.getString(cursor.getColumnIndex("id"));
        hoge.name = cursor.getString(cursor.getColumnIndex("name"));
        hoge.address = cursor.getString(cursor.getColumnIndex("address"));
        hoge.count = cursor.getInt(cursor.getColumnIndex("count"));
        hoge.flag = cursor.getInt(cursor.getColumnIndex("flag"));
    }
} finally {
    cursor.close();
}
SQLiteDatabase#rawQuery()を使った処理
Cursor cursor = db.rawQuery("SELECT * FROM table_name WHERE id=?", new String[]{"id5000"});
try {
    if (cursor.moveToNext()) {
        Hoge hoge = new Hoge();
        hoge.id = cursor.getString(cursor.getColumnIndex("id"));
        hoge.name = cursor.getString(cursor.getColumnIndex("name"));
        hoge.address = cursor.getString(cursor.getColumnIndex("address"));
        hoge.count = cursor.getInt(cursor.getColumnIndex("count"));
        hoge.flag = cursor.getInt(cursor.getColumnIndex("flag"));
    }
} finally {
    cursor.close();
}

結果

結果は以下の通り、rawQueryの方がわずかに速い。

メソッド 処理時間[msec]
SQLiteDatabase#query 0.76
SQLiteDatabase#rawQuery 0.68

これはSQLiteDatabase#query内部では以下のようにrawQueryを呼び出しているため。

SQLiteDatabase#query()
public Cursor queryWithFactory(CursorFactory cursorFactory,
        boolean distinct, String table, String[] columns,
        String selection, String[] selectionArgs, String groupBy,
        String having, String orderBy, String limit, CancellationSignal cancellationSignal) {
    acquireReference();
    try {
        String sql = SQLiteQueryBuilder.buildQueryString(
                distinct, table, columns, selection, groupBy, having, orderBy, limit);

        return rawQueryWithFactory(cursorFactory, sql, selectionArgs,
                findEditTable(table), cancellationSignal);
    } finally {
        releaseReference();
    }
}

ただし、これは1行のみ取得しているケースであり、複数行の場合は相対的に差は小さくなる。
...と思ったが、100行取得するケースの測定結果は以下の通りであり、そんなことはないかも。

メソッド 処理時間[msec]
SQLiteDatabase#query 7.69
SQLiteDatabase#rawQuery 6.31

1行 vs N行

ListViewなどでDBからデータを取得して表示するケースは多いと思う。
このとき、データやレイアウトによってはAdapter#getViewが呼ばれる度に毎回1行取得するとスクロール中にカクつくことがある。
そこで、1度に何行取得するのが良いのかを考える。

カラムでソート順が指定できる場合は以下のようなにLIMITとOFFSETを指定する。

LIMIT/OFFSET指定
Cursor cursor = db.rawQuery("SELECT * FROM table_name ORDER BY id LIMIT ? OFFSET ?", new String[]{String.valueOf(count), String.valueOf(offset)});
try {
    List<Hoge> hoges = new ArrayList<>(cursor.getCount());
    while (cursor.moveToNext()) {
        Hoge hoge = new Hoge();
        hoge.id = cursor.getString(cursor.getColumnIndex("id"));
        hoge.name = cursor.getString(cursor.getColumnIndex("name"));
        hoge.address = cursor.getString(cursor.getColumnIndex("address"));
        hoge.count = cursor.getInt(cursor.getColumnIndex("count"));
        hoge.flag = cursor.getInt(cursor.getColumnIndex("flag"));
        hoges.add(hoge);
    }
} finally {
    cursor.close();
}

カラムでソートできない場合は別途並び順を保持しておいて、以下のようにINを使用する。
この場合は取得したデータの並び順は不定なため、最後にソートする必要がある。

IN指定
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM table_name WHERE id IN (");
for (int i = 0; i < ids.length; ++i) {
    sql.append("?");
    if (i != ids.length - 1) {
        sql.append(",");
    }
}
sql.append(")");

Cursor cursor = db.rawQuery(sql.toString() ids);
try {
    List<Hoge> hoges = new ArrayList<>(cursor.getCount());
    while (cursor.moveToNext()) {
        Hoge hoge = new Hoge();
        hoge.id = cursor.getString(cursor.getColumnIndex("id"));
        hoge.name = cursor.getString(cursor.getColumnIndex("name"));
        hoge.address = cursor.getString(cursor.getColumnIndex("address"));
        hoge.count = cursor.getInt(cursor.getColumnIndex("count"));
        hoge.flag = cursor.getInt(cursor.getColumnIndex("flag"));
        hoges.add(hoge);
    }

    Collections.sort(hoges, new Comparator<Hoge>() {
        @Override
        public int compare(String lhs, String rhs) {
            return ids.indexOf(lhs) - ids.indexOf(rhs);
        }
    });
} finally {
    cursor.close();
}

結果

LIMIT/OFFSETを指定した場合のみであるが、結果は以下の通り。
行数と処理時間が比例関係になく、100行の場合が最も効率が良い。ただし、実際、何行にするかは絶対的な処理時間とメモリ使用量等の考慮が必要になる。

メソッド 1行[msec] 5行[msec] 10行[msec] 25行[msec] 50行[msec] 100行[msec]
SQLiteDatabase#rawQuery 0.70 0.88 1.18 2.18 3.09 5.58
23
24
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
23
24