2010-08-17 21 views
83

Ich muss eine ziemlich große XML-Datei analysieren (variierend zwischen etwa hundert Kilobyte und mehrere hundert Kilobyte), die ich mit Xml#parse(String, ContentHandler) mache. Ich teste das gerade mit einer 152KB-Datei.Android SQLite-Datenbank: Langsame Einfügung

Während des Parsens füge ich die Daten auch in eine SQLite-Datenbank ein, die ähnliche Aufrufe wie folgt verwendet: getWritableDatabase().insert(TABLE_NAME, "_id", values). All dies zusammen benötigt etwa 80 Sekunden für die 152 KB Testdatei (was zum Einfügen von etwa 200 Zeilen führt).

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

Ist es normal, dass die Datenbankoperationen einen so großen Overhead haben? Kann ich etwas dagegen tun?

Antwort

179

Sie sollten Batch-Einsätze durchführen.

Pseudocode:

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

Das extrem die Geschwindigkeit von Einsätzen in meine apps erhöht.

Update:
@Yuku bot eine sehr interessante Blog-Post: Android using inserthelper for faster insertions into sqlite database

+4

alle content auf diese Weise einfügen dauert nur eine Sekunde oder zwei. Danke vielmals! – benvd

+0

Ist es sicher, eine lang laufende Transaktion geöffnet zu haben, die die gesamte Dauer der XML-Parsing-Operation abdeckt und sie dann am Ende festlegt? Oder sollte die Liste der Einfügungen lokal im XML-Parser zwischengespeichert werden und dann eine kurzlebige Transaktion geöffnet und übergeben werden, sobald das Parsing abgeschlossen ist? –

+2

Wrapping meiner 60 Einsätze mit einer Transaktion erhöht die Leistung 10x. Wrapping es mit einer Transaktion und mit einer vorbereiteten Anweisung (SQLiteStatement) erhöht es 20x! – stefs

3

Wenn die Tabelle auf sie einen Index hat, sollten Sie es vor dem Einsetzen der Aufzeichnungen fallen lassen und es dann Zugabe zurück, nachdem Sie Ihr verpflichtet haben Aufzeichnungen.

12

Das Kompilieren der sql insert-Anweisung hilft, die Dinge zu beschleunigen. Es kann auch mehr Aufwand erfordern, um alles zu stützen und mögliche Injektionen zu verhindern, da es jetzt alles auf Ihren Schultern ist.

Ein anderer Ansatz, der auch Dinge beschleunigen kann, ist die nicht dokumentierte android.database.DatabaseUtils.InsertHelper-Klasse. Mein Verständnis ist, dass es kompilierte Einfügeanweisungen tatsächlich umschließt. Der Übergang von nicht kompilierten transierten Einfügungen zu kompilierten transizierten Einfügungen war für meine großen (200 K + Einträge), aber einfachen SQLite-Einfügungen etwa dreimal schneller (2ms pro Insert zu .6ms 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(); 
+0

und wie ContentValue in iHelp.bind (first_index, thing_1); ? –

1

Bei Verwendung eines 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; 

} 

dann die private Funktion den Einsatz (nach wie vor in Ihrem Content-Provider) auszuführen:

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


    } 
61

Seit Der von Yuku und Brett erwähnte InsertHelper ist jetzt deprecated (API Level 17), es scheint die r Die von Google empfohlene Alternative verwendet SQLiteStatement.

ich die Datenbank einfügen Methode wie folgt verwendet:

database.insert(table, null, values); 

Nachdem ich den folgenden Code, meine 500 Einsätze bis von 14,5 sec- nur 270 ms beschleunigt, auch einige ernsthafte Performance-Probleme erlebt, erstaunlich !

Hier ist, wie 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(); 
} 
+13

Ein Haken, um hier zu vermeiden: der Index in bindString ist 1-basiert und nicht 0 basiert –

+0

@qefzec Vielen Dank für diese Lösung .. Dies ließ nur die Einfügezeit in meiner App von 78 Sekunden auf 4 für 900 Zeilen hinzugefügt .. – csanonymus

+1

Danke mein Herr. 20000 Datensätze mit jeweils 6 Datenfeldern, einschließlich VARCHAR (80) von 4 Minuten bis 8 Sekunden. Eigentlich sollte dies als beste Antwort markiert werden, IMHO. – TomeeNS