Android SQLiteデータベース:挿入が遅い


91

かなり大きなXMLファイル(約100キロバイトから数百キロバイトまでの範囲)を解析する必要がありますXml#parse(String, ContentHandler)。これは、を使用して実行しています。私は現在、これを152KBファイルでテストしています。

解析中、次のような呼び出しを使用してSQLiteデータベースにデータを挿入しますgetWritableDatabase().insert(TABLE_NAME, "_id", values)。これらすべてを合わせて、152KBのテストファイル(約200行を挿入する)の場合、約80秒かかります。

すべての挿入ステートメントをコメントアウトすると(ただし、作成ContentValuesなど、他のものはすべて残します)、同じファイルにかかる時間はわずか23秒です。

データベース操作でこのような大きなオーバーヘッドが発生するのは正常ですか?それについて何かできますか?

回答:


190

バッチ挿入を行う必要があります。

疑似コード:

db.beginTransaction();
for (entry : listOfEntries) {
    db.insert(entry);
}
db.setTransactionSuccessful();
db.endTransaction();

これにより、アプリでの挿入速度が大幅に向上しました。

更新:
@Yukuは非常に興味深いブログ投稿を提供しました:SQLite データベースへの挿入を高速化するためにAndroidはinserthelperを使用しています


4
この方法ですべてのContentValuesを挿入すると、わずか1〜2秒しかかかりません。どうもありがとう!
benvd 2010

長期実行トランザクションを開いて、XML解析操作の全期間をカバーし、最後にコミットしても安全ですか?または、挿入のリストをXMLパーサー内でローカルにキャッシュし、解析が完了すると、短期間のトランザクションを開いてコミットする必要がありますか?
Graham Borland

2
60の挿入をトランザクションでラップすると、パフォーマンスが10倍向上しました。トランザクションでラップし、準備済みステートメント(SQLiteStatement)を使用すると、20倍に増加しました。
stefs

2
コメントをありがとうございます。私は20kのレコードを挿入、:-)それは8minsかかりましたが、トランザクションを使用した後、それはわずか20秒かかりました
ベア

2
このブログエントリでは、ほとんど隠されたInsertHelperoutofwhatbox.com/blog/2010/12/…を使用した別の最適化について説明します
Randy Sugianto 'Yuku'

68

YukuとBrettによって言及されたInsertHelperは現在非推奨(APIレベル17)であるため、Googleが推奨する適切な代替手段はSQLiteStatementを使用することです

私はこのようなデータベース挿入メソッドを使用しました:

database.insert(table, null, values);

深刻なパフォーマンスの問題もいくつか経験した後、次のコードにより500の挿入が14.5秒から270ミリにスピードアップしました。

SQLiteStatementの使用方法は次のとおりです。

private void insertTestData() {
    String sql = "insert into producttable (name, description, price, stock_available) values (?, ?, ?, ?);";

    dbHandler.getWritableDatabase();
    database.beginTransaction();
    SQLiteStatement stmt = database.compileStatement(sql);

    for (int i = 0; i < NUMBER_OF_ROWS; i++) {
        //generate some values

        stmt.bindString(1, randomName);
        stmt.bindString(2, randomDescription);
        stmt.bindDouble(3, randomPrice);
        stmt.bindLong(4, randomNumber);

        long entryID = stmt.executeInsert();
        stmt.clearBindings();
    }

    database.setTransactionSuccessful();
    database.endTransaction();

    dbHandler.close();
}

14
ここで避けるべき1つのキャッチ:bindStringのインデックスは1ベースであり、0ベースではありません
表示名

このsolution..Thisため@qefzecおかげで、あなただけの...追加900行のために4に78秒から私のアプリで挿入時間を落とした
csanonymus

1
ありがとうございます。4から8秒のVARCHAR(80)を含む、6つのデータフィールドを持つ20000レコード。実際、これは最良の答えとしてマークされるべきです、私見。
TomeeNS、2015年

1
すごい!デバイスと内部/外部メモリに応じて、一度に200のテストインサートのベンチマークで、インサートあたり15カラムで4100%〜10400%の改善が見られました。以前のパフォーマンスは私たちのプロジェクトが地面を離れる前に破滅していたでしょう。
フランク

13600行で15
DoruChidean

13

sql insertステートメントをコンパイルすると、処理速度が上がります。また、すべてが肩にかかっているので、すべてを補強して注射の可能性を防ぐために、より多くの労力が必要になる場合もあります。

また、物事をスピードアップできる別のアプローチは、文書化されていないandroid.database.DatabaseUtils.InsertHelperクラスです。私の理解では、コンパイルされた挿入ステートメントを実際にラップしています。コンパイルされていないトランザクションインサートからコンパイルされたトランザクションインサートに移行すると、大容量(200K +エントリ)の単純なSQLiteインサートで約3倍の速度(インサートあたり2ミリ秒から.6ミリ秒)になります。

サンプルコード:

SQLiteDatabse db = getWriteableDatabase();

//use the db you would normally use for db.insert, and the "table_name"
//is the same one you would use in db.insert()
InsertHelper iHelp = new InsertHelper(db, "table_name");

//Get the indices you need to bind data to
//Similar to Cursor.getColumnIndex("col_name");                 
int first_index = iHelp.getColumnIndex("first");
int last_index = iHelp.getColumnIndex("last");

try
{
   db.beginTransaction();
   for(int i=0 ; i<num_things ; ++i)
   {
       //need to tell the helper you are inserting (rather than replacing)
       iHelp.prepareForInsert();

       //do the equivalent of ContentValues.put("field","value") here
       iHelp.bind(first_index, thing_1);
       iHelp.bind(last_index, thing_2);

       //the db.insert() equilvalent
       iHelp.execute();
   }
   db.setTransactionSuccessful();
}
finally
{
    db.endTransaction();
}
db.close();

iHelp.bind(first_index、thing_1);にContentValueを追加する方法。?
Vasil Valchev 2015

3

テーブルにインデックスがある場合は、レコードを挿入する前にテーブルを削除し、レコードをコミットした後で再度追加することを検討してください。


1

ContentProviderを使用している場合:

@Override
public int bulkInsert(Uri uri, ContentValues[] bulkinsertvalues) {

    int QueryType = sUriMatcher.match(uri);
    int returnValue=0;
    SQLiteDatabase db = mOpenHelper.getWritableDatabase();

     switch (QueryType) {

         case SOME_URI_IM_LOOKING_FOR: //replace this with your real URI

            db.beginTransaction();

            for (int i = 0; i < bulkinsertvalues.length; i++) {
                //get an individual result from the array of ContentValues
                ContentValues values = bulkinsertvalues[i];
                //insert this record into the local SQLite database using a private function you create, "insertIndividualRecord" (replace with a better function name)
                insertIndividualRecord(uri, values);    
            }

            db.setTransactionSuccessful();
            db.endTransaction();                 

            break;  

         default:
             throw new IllegalArgumentException("Unknown URI " + uri);

     }    

    return returnValue;

}

次に、挿入を実行するプライベート関数(まだコンテンツプロバイダー内にあります):

       private Uri insertIndividualRecord(Uri uri, ContentValues values){

            //see content provider documentation if this is confusing
            if (sUriMatcher.match(uri) != THE_CONSTANT_IM_LOOKING_FOR) {
                throw new IllegalArgumentException("Unknown URI " + uri);
            }

            //example validation if you have a field called "name" in your database
            if (values.containsKey(YOUR_CONSTANT_FOR_NAME) == false) {
                values.put(YOUR_CONSTANT_FOR_NAME, "");
            }

            //******add all your other validations

            //**********

           //time to insert records into your local SQLite database
           SQLiteDatabase db = mOpenHelper.getWritableDatabase();
           long rowId = db.insert(YOUR_TABLE_NAME, null, values);           

           if (rowId > 0) {
               Uri myUri = ContentUris.withAppendedId(MY_INSERT_URI, rowId);
               getContext().getContentResolver().notifyChange(myUri, null);

               return myUri;
           }


           throw new SQLException("Failed to insert row into " + uri);


    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.