LoginSignup
40
44

More than 5 years have passed since last update.

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

Last updated at Posted at 2015-01-29

いまさらながら、AndroidのSQLiteのパフォーマンスを比較してみた。

環境

Nexus5, Android 4.4.4

テーブル

こんなかんじ。深い意味はない。

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

1. SQLiteDatabase#insert

おそらく、ほとんどの人が最初に学ぶ方法。
SQLを知らなくても書けるし、わかりやすいが…

db.beginTransaction();
try {
    final int columnCount = 5;
    final ContentValues values = new ContentValues(columnCount);
    for (int i = 0; i < totalCount; ++i) {
        values.clear();
        values.put("id", "id" + i);
        values.put("name", "name" + i);
        values.put("address", (String) null);
        values.put("count", i);
        values.put("flag", 0);
        db.insert("table_name", null, values);
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

2. SQLiteDatabase#execSQL(単一行)

execSQLを使った方法。
Android Developersには、SELECT/INSERT/UPDATE/DELETE以外のときに使う、との記載があるが、戻り値不要なら使える。

db.beginTransaction();
try {
    for (int i = 0; i < totalCount; ++i) {
        db.execSQL("INSERT INTO table_name VALUES (?,?,?,?,?)",
            new String[] {"id" + i, "name" + i, null, String.valueOf(i), String.valueOf(0)});
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

3. SQLiteDatabase#execSQL(複数行)

ずいぶん長くなってきた。
SQLite3.7.11から使える1回のINSERTで複数行追加できるSQL文。
Android 4.1以降はSQLite3.7.11以降らしいが、それより前のバージョンだとエラーになるので注意。
下記の例では10行ずつINSERTしている。

db.beginTransaction();
try {
    final int columnCount = 5;
    final int rowCount = 10;
    final String[] bindArgs = new String[columnCount * rowCount];
    final StringBuilder sql = new StringBuilder("INSERT INTO table_name VALUES (?,?,?,?,?)");
    for (int i = 0; i < rowCount - 1; ++i) {
        sql.append(", (?,?,?,?,?)");
    }
    for (int i = 0; i < totalCount; ++i) {
        final int index = columnCount * (i % rowCount);
        bindArgs[index] = "id" + i;
        bindArgs[index + 1] = "name" + i;
        bindArgs[index + 2] = null;
        bindArgs[index + 3] = String.valueOf(i);
        bindArgs[index + 4] = String.valueOf(0);
        if (i % rowCount == rowCount - 1) {
            db.execSQL(sql.toString(), bindArgs);
        }
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

4. SQLiteDatabase#execSQL(UNION)

SQLite3.7.11以前でも使えるUNIONを用いた1回のINSERTで複数行追加できるSQL文。
Android2.3.3でも使えた。

db.beginTransaction();
try {
    final int columnCount = 5;
    final int rowCount = 10;
    final StringBuilder sql = new StringBuilder("INSERT INTO table_name SELECT ?,?,?,?,?");
    final String[] bindArgs = new String[columnCount * rowCount];
    for (int i = 0; i < rowCount - 1; ++i) {
        sql.append(" UNION ALL SELECT ?,?,?,?,?");
    }
    for (int i = 0; i < totalCount; ++i) {
        final int index = columnCount * (i % rowCount);
        bindArgs[index] = "id" + i;
        bindArgs[index + 1] = "name" + i;
        bindArgs[index + 2] = null;
        bindArgs[index + 3] = String.valueOf(i);
        bindArgs[index + 4] = String.valueOf(0);
        if (i % rowCount == rowCount - 1) {
            db.execSQL(sql.toString(), bindArgs);
        }
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

5. SQLiteStatement#executeInsert(単一行)

SQLiteStatementを使った方法。
closeが必要なため、try-finallyが増えるのと、型毎にbindメソッドが異なるのが面倒。

db.beginTransaction();
try {
    final SQLiteStatement statement = db.compileStatement("INSERT INTO table_name VALUES (?,?,?,?,?)");
    try {
        for (int i = 0; i < totalCount; ++i) {
            statement.bindString(1, "id" + i);
            statement.bindString(2, "name" + i);
            statement.bindNull(3);
            statement.bindLong(4, i);
            statement.bindLong(5, 0);
            statement.executeInsert();
        }
    } finally {
        statement.close();
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

6. SQLiteStatement#executeInsert(複数行)

3と同じ複数行INSERTを使った方法。
1回のINSERTで追加する行数が追加する行数の約数になっていないと
2回SQLiteStatementを作成しないといけないのが、大きな欠点。

db.beginTransaction();
try {
    final int columnCount = 5;
    final int rowCount = 10;
    final StringBuilder sql = new StringBuilder("INSERT INTO table_name VALUES (?,?,?,?,?)");
    for (int i = 0; i < rowCount - 1; ++i) {
        sql.append(", (?,?,?,?,?)");
    }
    final SQLiteStatement statement = db.compileStatement(sql.toString());
    try {
        for (int i = 0; i < totalCount; ++i) {
            final int index = columnCount * (i % rowCount);
            statement.bindString(index + 1, "id" + i);
            statement.bindString(index + 2, "name" + i);
            statement.bindNull(index + 3);
            statement.bindLong(index + 4, i);
            statement.bindLong(index + 5, 0);
            if (i % rowCount == rowCount - 1) {
                 statement.executeInsert();
            }
        } finally {
            statement.close();
        } 
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

7. SQLiteStatement#executeInsert(UNION)

4と同じUNIONを使った方法。
6と同じく、2回SQLiteStatementを使わないといけない可能性がある、という欠点がある。

db.beginTransaction();
try {
    final int columnCount = 5;
    final int rowCount = 10;
    final StringBuilder sql = new StringBuilder("INSERT INTO table_name SELECT ?,?,?,?,?");
    for (int i = 0; i < rowCount - 1; ++i) {
        sql.append(" UNION ALL SELECT ?,?,?,?,?");
    }
    final SQLiteStatement statement = db.compileStatement(sql.toString());
    try {
        for (int i = 0; i < totalCount; ++i) {
            final int index = columnCount * (i % rowCount);
            statement.bindString(index + 1, "id" + i);
            statement.bindString(index + 2, "name" + i);
            statement.bindNull(index + 3);
            statement.bindLong(index + 4, i);
            statement.bindLong(index + 5, 0);
            if (i % rowCount == rowCount - 1) {
                statement.executeInsert();
            }
        } finally {
            statement.close();
        }
    }
    db.setTransactionSuccessful();
} finally {
    db.endTransaction();
}

結果

それぞれの方法で100行、1000行、10000行追加した場合の処理時間(msec)は以下の通り。
1回のINSERTで複数行追加できる方法では10行、50行、100行で試した。

単一行については、5(executeInsert)がそれぞれ1(insert)、2(execSQL)の1.6倍、1.3倍程度と最も速い。
複数行についても、6,7(executeInsert)が最も速い。また、追加する全行数が多くなるほど、5に対しても速くなる。今回は、1回あたりの追加行数は50行が一番速いケースが多かったが、テーブルに依存すると思われる。

6,7は上にあげた欠点があるのと、SQL文が長くなりすぎると落ちるため、
基本的には5の方法を使い、1度に大量データを保存するようなアプリでパフォーマンスが求められる場合は6,7の方法を検討しようと思う。

方法 1度に追加する行数 100行 1000行 10000行
1. SQLiteDatabase#insert 1 29.61 234.2 2166.4
2. SQLiteDatabase#execSQL(単一行) 1 21.95 175.0 1717.9
3. SQLiteDatabase#execSQL(複数行) 10 14.43 93.2 876.8
50 13.83 75.5 823.5
100 15.21 88.6 799.8
4. SQLiteDatabase#execSQL(UNION) 10 14.65 92.1 798.1
50 14.34 83.1 784.1
100 14.61 83.0 835.1
5. SQLiteStatement#executeInsert(単一行) 1 17.28 126.1 1122.3
6. SQLiteStatement#executeInsert(複数行) 10 13.27 77.9 697.2
50 13.04 54.6 667.5
100 13.5 59.8 691.7
7. SQLiteStatement#executeInsert(UNION) 10 13.93 81.2 681.0
50 13.02 97.8 661.7
100 13.7 98.4 672.7
40
44
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
40
44