Androidアプリ開発者のためのTips集。「データベース処理が重いと感じたら」――今回は、「SQLite」データベースの処理をより効率的に実行するための便利なTipsをいくつか紹介する。
用途 | 基本 |
---|---|
カテゴリ | データ処理 |
レベル | 中級 |
動作確認環境 | Android 2.3.3(GingerBread) エミュレータにて動作確認 |
備考 | 今回のTipsは上記環境で動作確認・検証を行っています |
前回、前々回に引き続き、今回もデータベース「SQLite」について取り上げる。
今回は、データベース処理をより効率的に実行するためのTipsをいくつか紹介しよう。
データベースを長期間使用し続け、データの追加・削除・変更を繰り返していると、データベースファイルの内容にムダな空きスペースができたり、ファイル内でのデータの並び順が複雑になったりして、パフォーマンスが次第に低下してしまう。
こういった状況を避けるために、SQLiteでは「Vacuum」という機能が用意されている。Vacuumは、HDDの最適化に似た機能で、未使用の空き領域を削除したり、断片化したデータを格納し直したりといった“ファイル構造の整理”を行ってくれる。
AndroidでVacuumを実行するには、SQLiteDatabase#execSQL()メソッドを使用する。
/** Vacuum の実行. */ private void doVacuum( SQLiteDatabase db ){ db.execSQL( "vacuum" ); }
ただし、Vacuum処理が完了するまでには多少の時間を要する。そのため、実行タイミングには気を使う必要がある。できることならば、別スレッドで行うことが望ましい。
SQLiteは、トランザクションに対応している。トランザクションを使用すると、複数のデータ操作をひとまとめにして取り扱ったり、連続したデータ操作処理の途中における不整合の発生を防いだり、一連のデータ操作をまとめて巻き戻したり(元に戻したり)することができる。
さらに、データの更新処理を連続して行うような場合、トランザクションを使用すると大幅に処理速度が向上する。
まずは、通常の処理。サンプルとして、1000件のランダムな名前データをひたすらデータベースに挿入するケースを考えてみよう。
// 名字20種 private static final String[] FAMILY_NAMES = new String[]{ "佐藤", "鈴木", "高橋", "田中" , "渡辺", "伊藤" , "山本", "中村", "小林", "斎藤", "加藤", "吉田", "山田", "佐々木", "山口", "松本" , "井上", "木村", "林" , "清水", }; // 名前20種 private static final String[] FIRST_NAMES = new String[]{ "太郎" , "次郎", "健太郎", "康平", "大介", "達也", "直人", "徹", "祐介", "和也", "さくら", "佳奈", "久美子", "結衣", "香織", "成美", "智美", "瞳", "麻美", "凜" , }; /** 大量のデータ挿入. */ private void insertManyData( SQLiteDatabase db ){ Random r = new Random( System.currentTimeMillis() ); ContentValues val = new ContentValues(); // ランダムなエントリ1000件をデータベースにインサート for( int i = 0; i < 1000; i++ ){ // ランダムな名前と年齢で挿入 val.put( "name", FAMILY_NAMES[r.nextInt(20)] + FIRST_NAMES[r.nextInt(20)] ); val.put( "age" , r.nextInt(50) ); db.insert( "profile_table", null, val ); } }
これをエミュレータ上で実行してみたところ、全件挿入が終わるのに約7.8秒かかった(画像1)。
では、この処理でトランザクションを使うとどのようになるだろうか。以下を見てほしい。
/** 大量のデータ挿入(トランザクションを使用). */ private void insertManyDataWithTransaction( SQLiteDatabase db ){ Random r = new Random( System.currentTimeMillis() ); ContentValues val = new ContentValues(); // トランザクションを確実に終了するために、try{}〜finally{}にする try{ // トランザクション開始 db.beginTransaction(); // ランダムなエントリ1000件をデータベースにインサート for( int i = 0; i < 1000; i++ ){ // ランダムな名前と年齢で挿入 val.put( "name", FAMILY_NAMES[r.nextInt(20)] + FIRST_NAMES[r.nextInt(20)] ); val.put( "age" , r.nextInt(50) ); db.insert( "profile_table", null, val ); } // 全件正常に挿入したら、トランザクション成功 db.setTransactionSuccessful(); } finally{ // トランザクションの終了 db.endTransaction(); } }
まず、SQLiteDatabase#beginTransaction()メソッドを呼んでトランザクションを開始する。その後、ひとまとめにしたいデータ操作(上記サンプルコードの例では、1000件の名前データを挿入)を次々と実行し、全て終了したところで、SQLiteDatabase#setTransactionSuccessful()メソッドを呼び出し、最後にSQLiteDatabase#endTransaction()メソッドを呼んでトランザクションを終了する。
もし、連続したデータ操作のどこかでエラーが発生した場合、setTransactionSuccessful()を呼ばない状態でendTransaction()を実行してしまうと、一連のデータ操作は全て無効になってしまう。
上記のトランザクションを使用した処理を、同じくエミュレータ上で実行してみたところ、約1.3秒で全件挿入が完了(画像2)した。
通常、SQLiteのデータ更新は、1回ごとにファイルを更新するため、非常に速度が遅い。対して、トランザクションを使用した場合、ファイルへの書き込みはトランザクション終了時に1回だけ行われる。
今回の例でいうと、トランザクションを使用しない場合、1万回のファイル書き込みが発生するのだが、トランザクションを使用した場合、ファイル書き込みはたったの1回しか行われない。その結果、実行速度が格段に速くなるというわけだ。
データ更新の件数が多ければ多いほどトランザクションによる速度向上が見込めるが、数件程度のデータ更新の場合は、書き込み回数の差が少ないため、あまり効果が見込めない。
Copyright © ITmedia, Inc. All Rights Reserved.