SQLiteのinsertが遅いですって

こんにちは!こんにちは!

って書くととんでもないtipsを披露しそうに見える。がそういうことではない。
初心者がデータベースに触ってみたお話。

SQLiteDatabase

データベースってなんかよくわからないよね。で、Androidアプリを作りながらよくわからないなりに色んなところを参考にしてデータベース触ってるんですよ。
で、ちょっと初回起動で一気にデータを作るようなものを書いてたんですがどうも遅い。String4カラムぐらいが200件ちょっと。
insert以外もいろんな処理があるので適当なんだけどざっくり言って60s〜70sぐらいかかってました。いくらプログレスダイアログ表示させてるからと言ってこの間ユーザに待たせるなんてのはアレな話ですね。しかもこれはとりあえずのデータで、実際はもっと大量に処理しなきゃいけない。
 なんか別の実装方法考えなきゃいけないかなーと思ってたらtwitterで某riosu氏にトランザクションはどうかと教えてもらった。
トランザクションの説明は余所に任せるとして、トランザクションの表面的な処理をAndroid的な仕組みで言えば(あくまで仕組み)Preferences.Editorみたいなもので、commitして初めて完結する、というか何かあったらrollbackしてなかったことにする感じらしい。
 でSQLiteではどうなってるのかと調べると、「insertが遅い」という記事がいっぱい出てきた。insertの内部でいちいちトランザクションが行われ、たとえばinsertを3回実行すると


begin
insert
commit
begin
insert
commit
begin
insert
commit
適当に書くとこういうことで、ちゃんとトランザクションを宣言すれば

begin
insert
insert
insert
commit
このようになって早くなる、ということらしい。

実際

やってみます。
コードからわかるANDROIDプログラミングのしくみ
Androidでデータベース扱う記事を読むとよくSQLiteOpenHelperクラスを拡張するやつが出てきます。例えばDBOpenHelper extends SQLiteOpenHelperとか。
今回のプログラムは↑の「コードからわかる〜」に出てくるサンプルをもとに作っていて、よくあるDBOpenHelperクラスをさらにラップしてます。例えばDBHelperとか。
そのSQLiteのinsertを実行するためのinsertメソッド。後で読み返してわからないと(自分が)困るので適当に改造して置いておきます。

// DBHelper.java
// http://developer.android.com/intl/ja/reference/android/database/sqlite/SQLiteDatabase.html
// db: SQLiteDatabase
	public void insert(Word word){
		ContentValues values = this.getContentValues(word);
		this.db.insert(DBHelper.DB_TABLE_NAME, null, values);
	}

抜き出されてもアレとは思うけど適当に読み替えてもらうとして。
要はこれを200回なり実行するわけです。これをトランザクションで処理するには。ネットを見るとPHPとかのプログラムをみるとBEGINとかやって終わったらCOMMITとやればいいっぽいのでとりあえずやってみる。

// DBHelper.java
            //トランザクション開始しているか
            private boolean beginTransaction = false;

            // トランザクション trueで開始、falseで終了
            public void testTransaction(boolean transaction){
		if(transaction){
			// トランザクション開始
			beginTransaction = true;
			db.execSQL("BEGIN");
		}else if(beginTransaction){
			// トランザクション終了
			beginTransaction = false;
			db.execSQL("COMMIT");
		}
	}

改めてみるとひどい…
そして呼ぶ側。雰囲気で。

         public void initialize(Context context, DBHelper dbHelper) {
		dbHelper.testTransaction(true);

		List<Word> words = getDummyWords();
		for(Word w: words){ dbHelper.insert(w); }
		
		dbHelper.testTransaction(false);
	}

SQLiteDatabase#execSQL()でまんま"BEGIN"とか書いてるだけっていう。
こんな簡単でいいのかと実行してみると、効果ばっちりでものすごい早くなりました。十数分の一ぐらいの時間。1分以上かかってたのが数秒です。すばらしい。insertだけでちゃんと測ったら世にあるデータの通りもっと早くなってるでしょう。

しかし

じゃあちゃんと書こうかな、などといじくってるうちに気づきました

あれ…?


これは…!

つまり…
初めからトランザクション用のメソッドがあったんだよ!!
な、なんだってー
採用します。
だって例まで出てるし。適当な処理書いてるだけで心もとないので。
こういうところって結局Androidの本とかネットの記事読んでもなかなか出てこないんですよね。Android用にSQLの解説はあってもinsert()はこんなんですよーとかやって終わりとかね。しょうがないんだけどさ(DBわからん奴はAndroidの前にDB勉強してこいとも)。とりあえず忘れないうちにざっと書いておいた。無駄多すぎるけどまあいいか[memo]だし。確信持ってる話じゃないとなかなか解説っぽくかけないよね。まだまだレベルが足りんのです。

忘れてた

 これでDB初期化の時間が短くなってもまだ問題があって、「初期化に必要なデータをどう用意するか」。
とりあえずはstring-arrayのリソース何個も持ってるんだけど今後のメンテ的につらいと思われる。あと初期化した後使わないのに無駄にアプリの容量がでかくなってしまう。インストール時にデータベース勝手に配置してくれればマシなんだけど。
 ネット上に置いて初回起動時に読みに行くというのが一番楽でベターな感じだろうか。あとはファイルをダウロードしてSDに置いてもらうとか。これならPCからでもできるけどひと手間かかってしまう。
 なんかいい方法ないかなあ