it-swarm.com.de

Android SQLite-Datenbank: langsames Einfügen

Ich muss eine ziemlich große XML-Datei (zwischen ungefähr hundert und mehreren hundert Kilobyte) analysieren, wobei ich Xml#parse(String, ContentHandler) verwende. Ich teste dies derzeit mit einer 152KB-Datei.

Während des Parsens füge ich die Daten auch in eine SQLite-Datenbank ein, indem ich Aufrufe ähnlich den folgenden verwende: getWritableDatabase().insert(TABLE_NAME, "_id", values). All dies zusammen dauert ungefähr 80 Sekunden für die 152-KB-Testdatei (was auf das Einfügen von ungefähr 200 Zeilen hinausläuft).

Wenn ich alle insert-Anweisungen auskommentiere (aber alles andere, wie das Erstellen von ContentValues usw., belasse), dauert dieselbe Datei nur 23 Sekunden.

Ist es normal, dass die Datenbankoperationen einen so hohen Aufwand verursachen? Kann ich etwas dagegen unternehmen?

90
benvd

Sie sollten Batch-Inserts durchführen.

Pseudocode:

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

Das hat die Geschwindigkeit der Einfügungen in meinen Apps extrem erhöht.

Aktualisieren:
@ Yuku lieferte einen sehr interessanten Blog-Beitrag: Android verwendet inserthelper für schnellere Einfügungen in die SQLite-Datenbank

188
WarrenFaith

Da der von Yuku und Brett erwähnte InsertHelper veraltet ist (API-Level 17), scheint es die richtige Alternative zu sein, die von Google empfohlen wird, SQLiteStatement zu verwenden.

Ich habe die Datenbankeinfügemethode wie folgt verwendet:

database.insert(table, null, values);

Nachdem ich auch einige ernsthafte Leistungsprobleme hatte, beschleunigte der folgende Code meine 500 Einfügungen von 14,5 Sekunden auf nur 270 ms, umwerfend!

So habe ich SQLiteStatement verwendet:

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();
}
68
qefzec

Das Kompilieren der Anweisung sql insert beschleunigt die Ausführung. Es kann auch mehr Mühe erfordern, alles abzustützen und eine mögliche Injektion zu verhindern, da jetzt alles auf Ihren Schultern liegt.

Ein weiterer Ansatz, der die Dinge ebenfalls beschleunigen kann, ist die unterdokumentierte Android.database.DatabaseUtils.InsertHelper-Klasse. Mein Verständnis ist, dass es tatsächlich kompilierte Einfügeanweisungen umschließt. Die Umstellung von nicht kompilierten transaktierten Inserts auf kompilierte transaktierte Inserts ergab für meine großen (über 200.000 Einträge), aber einfachen SQLite-Inserts einen Geschwindigkeitsgewinn von etwa dem Dreifachen (2 ms pro Insert auf 0,6 ms pro Insert).

Beispielcode:

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();
13
Brett

Wenn die Tabelle einen Index enthält, sollten Sie ihn löschen, bevor Sie die Datensätze einfügen, und ihn dann wieder hinzufügen, nachdem Sie Ihre Datensätze festgeschrieben haben.

3
kaD'argo

Wenn Sie einen ContentProvider verwenden:

@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;

}

Dann die private Funktion, um die Einfügung durchzuführen (immer noch in Ihrem Inhaltsanbieter):

       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);


    }
1