SQLite(2)

2011/8/30


SQLiteにおいてINSERT文の実行方法は複数ある。
  1. SQLiteDatabase.insert()メソッド
  2. SQLiteDatabase.execSQL()メソッドでINSERT文を1件づつ実行
  3. トランザクション+INSERT文
  4. プリコンパイルSQL
  5. トランザクション+プリコンパイルSQL
それぞれの方法で処理速度は大きく変わる。SQLiteDatabase.insert()メソッドはもっとも遅く、次いで、INSERT文であり、プリコンパイルSQLはもっとも速いと予想できる。 また、トランザクションを使用しないよりは、トランザクションを使用した方がスピードが速くなると予想できる。 さらにデータベース本体を作成する場所によっても応答速度は変わる。
  1. ファイルとしてデータベースを作成
  2. メモリ上にデータベースを作成
もちろんメモリ上で処理した方が応答は速いだろう。

では、Android端末ではどの程度の違いがあるだろうか?

main.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent" android:orientation="vertical" android:weightSum="1">
<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="@string/hello"
    />
<LinearLayout android:layout_height="wrap_content" android:id="@+id/linearLayout1" android:layout_width="fill_parent">
    <TextView android:text="レコード数" android:layout_weight="0.1" android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/textView2"></TextView>
    <EditText android:layout_height="wrap_content" android:layout_width="wrap_content" android:id="@+id/editText1" android:layout_weight="0.1" android:inputType="number" android:text="1000">
    </EditText>
    <Button android:layout_weight="0.1" android:id="@+id/button1" android:text="Start" android:layout_height="wrap_content" android:layout_width="wrap_content" android:onClick="onClick"></Button>
</LinearLayout>
<TextView android:id="@+id/textView1" android:text="INSERT処理時間を計測" android:layout_width="fill_parent" android:layout_height="wrap_content"></TextView>
</LinearLayout>

MyDBクラス

基本的には、SQLite(1)で使用しているMyDBクラスと同じである。
データベースを作成場所を指定するためにコンストラクタにデータベース名(name)を入力できるようにしている。
また、INSERTするレコード数(dbmax)を外部から設定するためにアクセサメソッドsetDbmax(int dbmax)を用意した。
テストに使用するテーブルの構造は簡単なもの(id INTEGER PRIMARY KEY AUTOINCREMENT, n int)としている。
package org.maibun.TEST6;

import android.content.ContentValues;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteStatement;
import android.util.Log;

public class MyDB extends SQLiteOpenHelper {

	private int dbmax = 1000;

	public MyDB(Context context, String name, CursorFactory factory, int version) {
		super(context, name, factory, version);
	}

	public MyDB(Context context, String name) {
		super(context, name, null, 1);
	}

	@Override
	public void onCreate(SQLiteDatabase db) {
		for(int i=0; i<5;i++)
			db.execSQL("CREATE TABLE table"+i+"(id INTEGER PRIMARY KEY AUTOINCREMENT, n int);");
	}

	@Override
	public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
		for(int i=0; i<5;i++)
			db.execSQL("DROP TABLE IF EXISTS table"+i+";");
		onCreate(db);
	}
	
	//----------------------------------------------------------
	/**
	 * @param dbmax the dbmax to set
	 */
	public void setDbmax(int dbmax) {
		this.dbmax = dbmax;
	}

	//----------------------------------------------------------
	public void insert0(SQLiteDatabase db) {
		try{
			ContentValues cv = new ContentValues();
			for (int i = 0; i < dbmax; i++) {
				cv.put("n", i);
				db.insert("table0", null, cv);
			}
		}catch (Exception e){
			Log.e("TEST6","insert0 "+e.toString());
		}
	}

	public void insert1(SQLiteDatabase db) {
		try {
			for (int i = 0; i < dbmax; i++) {
				String s = "INSERT INTO table1(n) VALUES(" + i + ");";
				db.execSQL(s);// 1件だけinsert
			}
		} catch (Exception e) {
			Log.e("TEST6", "insert1 " + e.toString());
		}
	}
	
	public void insert2(SQLiteDatabase db) {
		try {
			db.beginTransaction();
			for (int i = 0; i < dbmax; i++) {
				String s = "INSERT INTO table1(n) VALUES(" + i + ");";
				db.execSQL(s);// 1件だけinsert
			}
			db.setTransactionSuccessful();
		} catch (Exception e) {
			Log.e("TEST6", "insert2 " + e.toString());
		} finally {
			db.endTransaction();
		}
	}
	
	public void insert3(SQLiteDatabase db){
		try{
			SQLiteStatement sqlite = db.compileStatement("INSERT INTO table3(n) VALUES (?);"); //パラメータを使ってプリコンパイルSQL
			for(long i=0; i<dbmax; i++){
				sqlite.bindLong(1,i); sqlite.executeInsert();
			}
		}	catch (Exception e){
			Log.e("TEST6","insert3 "+e.toString());
		}
	}

	public void insert4(SQLiteDatabase db) {
		try {
			db.beginTransaction();
			SQLiteStatement sqlite = db.compileStatement("INSERT INTO table4(n) VALUES (?);"); // パラメータを使ってプリコンパイルSQL
			for (int i = 0; i < dbmax; i++) {
				sqlite.bindLong(1, i);
				sqlite.executeInsert();
			}
			db.setTransactionSuccessful();
		} catch (Exception e) {
			Log.e("TEST6", "insert4 " + e.toString());
		} finally {
			db.endTransaction();
		}
	}

}

TEST6Activiryクラス

レコード数はtextView1で与える。数値としてパースできない場合は、dbmax=20としている。
package org.maibun.TEST6;

import android.app.Activity;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;

public class TEST6Activity extends Activity {
	/** Called when the activity is first created. */
	public Button button1 ;
	public TextView textView1;
	public EditText editText1;
	private long[] laptime = new long[8];
	private MyDB[] myDB = new MyDB[2];
	private SQLiteDatabase[] db = new SQLiteDatabase[2];

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		button1 = (Button) this.findViewById(R.id.button1);
		textView1 = (TextView) this.findViewById(R.id.textView1);
		editText1 = (EditText) this.findViewById(R.id.editText1);
		
		myDB[0] = new MyDB(this,"mydb");
		myDB[1] = new MyDB(this,null);
		for(int j=0; j<myDB.length; j++){
			db[j] = myDB[j].getWritableDatabase();
			myDB[j].onUpgrade(db[j], 0, 1);
		}
	}

	public void onClick(View view){
		int dbmax;
		String[] s={"--DISK--\n","--Memory--\n"};
		try{
			dbmax = Integer.parseInt(editText1.getText().toString() );
			
		} catch (Exception e) {
			dbmax=20;
		}
		s[0] = "レコード数 " +  dbmax + "\n" + s[0];
		for(int j=0; j<myDB.length; j++){
			myDB[j].setDbmax( dbmax );
			laptime[0] = android.os.SystemClock.uptimeMillis();
			for(int i=0;i<5; i++){
				switch(i){
				case 0 :	myDB[j].insert0(db[j]); break;
				case 1 :	myDB[j].insert1(db[j]); break;
				case 2 :	myDB[j].insert2(db[j]); break;
				case 3 :	myDB[j].insert3(db[j]); break;
				case 4 :	myDB[j].insert4(db[j]); break;
				default : break;
				}
			laptime[i+1] = android.os.SystemClock.uptimeMillis();
			s[j] += i+": "+ (laptime[i+1]-laptime[i]) + " mSec\n";
			}
		}
		textView1.setText(s[0].toString()+"\n"+s[1].toString()+"OK\n");
	}
}

実行結果

  1. データベースをメモリ上に作成すると応答速度は圧倒的に速い。
  2. SQLiteDatabase.insert()メソッドは遅いが、INSERT文を1件づつ実行、プリコンパイルSQLも遅い。
  3. トランザクションを使うと劇的に速くなる。
  4. 「(2)トランザクション+INSERT文」において、データベースをメモリ上に作成するよりデータベースをファイルとして作成したほうが、わずかにスピードが速いように見えるが、これは正しくない。数回テストを繰り返すと、メモリ上のデータベースのほうが速いときもあり、ファイルとして作成したデータベースのほうが速いときもある。
  5. 「(4)トランザクション+プリコンパイルSQL」では、データベースをファイルとして作成するよりデータベースをメモリ上に作成するよりしたほうがわずかにスピードが速いが、大きな差ではない。
(64ビット環境で測定)
方式ファイルとして作成メモリ上に作成
(0)SQLiteDatabase.insert()メソッド4385 ミリ秒1202 ミリ秒
(1)INSERT文を1件づつ実行3448658
(2)トランザクション+INSERT文646721
(3)プリコンパイルSQL3397281
(4)トランザクション+プリコンパイルSQL263254

開発環境によりエミュレータの処理速度はかなり違う。
開発環境1 (64bit開発環境) 開発環境2 (32bit開発環境)
OS : Fedora 14 X86_64
CPU: AMD Phenom(tm) II X4 910e Processor 2.6GHz
Mem: 8GB
OS : Fedora 14 X86
CPU: Intel Celeron(R) Dual-Core CPU T3000 @ 1.80GHz
Mem: 2GB
SQLite(2)

SQLite(2)


IS01でテスト

SQLite(2)

実物(IS01)でテストした。
エミュレータと実物では実効速度が違う。
  1. データベースをファイルとして作成すると、実機はエミュレータより遅くなる。
  2. データベースをメモリ上に作成すると、実機の応答速度はエミュレータより速い。
  3. 実機ではトランザクションの効果が大きい。

もっとたくさん

SQLite(2)

参考までに、メモリ上にデータベースをつくり、「(4)トランザクション+プリコンパイルSQL」でINSERT処理するレコード数を増やしたとき、 処理時間はレコード数に対して線形に推移し、1000件で213ミリ秒、106件で263秒(4分23秒)になった。