Database SQLite Android: inserimento lento


91

Ho bisogno di analizzare un file XML abbastanza grande (che varia tra un centinaio di kilobyte e diverse centinaia di kilobyte), che sto utilizzando Xml#parse(String, ContentHandler). Attualmente lo sto testando con un file da 152 KB.

Durante l'analisi, ho anche inserire i dati in un database SQLite utilizzano le chiamate simile al seguente: getWritableDatabase().insert(TABLE_NAME, "_id", values). Tutto questo insieme richiede circa 80 secondi per il file di prova da 152 KB (che si riduce all'inserimento di circa 200 righe).

Quando commento tutte le istruzioni di inserimento (ma lascio tutto il resto, come la creazione, ContentValuesecc.) Lo stesso file richiede solo 23 secondi.

È normale che le operazioni del database abbiano un overhead così grande? Posso fare qualcosa al riguardo?

Risposte:


190

Dovresti inserire batch.

Pseudocodice:

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

Ciò ha aumentato notevolmente la velocità degli inserimenti nelle mie app.

Aggiornamento:
@Yuku ha fornito un post sul blog molto interessante: Android utilizza inserthelper per inserimenti più rapidi nel database sqlite


4
L'inserimento di tutti i ContentValues ​​in questo modo richiede solo uno o due secondi. Molte grazie!
benvd

È sicuro avere una transazione di lunga durata aperta, che copra l'intera durata dell'operazione di analisi XML e quindi eseguirne il commit alla fine? Oppure l'elenco degli inserimenti dovrebbe essere memorizzato nella cache locale all'interno del parser XML e quindi avere una transazione di breve durata aperta e impegnata una volta completata l'analisi?
Graham Borland

2
avvolgere i miei 60 inserti con una transazione ha aumentato le prestazioni di 10 volte. avvolgendolo con una transazione e utilizzando un'istruzione preparata (SQLiteStatement) è aumentato di 20 volte!
stefs

2
grazie benvd per il commento. Stavo inserendo 20k record, ci sono voluti circa 8 minuti ma dopo aver usato una transazione ci vogliono solo 20 secondi :-)
Orso

2
Questo post di blog discute un'altra ottimizzazione usando il quasi nascosto InsertHelper outofwhatbox.com/blog/2010/12/…
Randy Sugianto 'Yuku'

68

Poiché InsertHelper menzionato da Yuku e Brett è ora deprecato (livello API 17), sembra che la giusta alternativa consigliata da Google stia utilizzando SQLiteStatement .

Ho usato il metodo di inserimento del database in questo modo:

database.insert(table, null, values);

Dopo aver riscontrato anche alcuni seri problemi di prestazioni, il codice seguente ha velocizzato i miei 500 inserimenti da 14,5 secondi a soli 270 ms , incredibile!

Ecco come ho usato 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
Un problema da evitare qui: l'indice in bindString è basato su 1 e non su 0
Nome visualizzato

@qefzec Grazie per questa soluzione..Questo ha appena ridotto il tempo di inserimento nella mia app da 78 secondi a 4 per 900 righe aggiunte ..
csanonymus

1
Grazie Signore. 20000 record con 6 campi di dati ciascuno, incluso VARCHAR (80) da 4 minuti a 8 secondi. In realtà questo dovrebbe essere contrassegnato come la migliore risposta, IMHO.
TomeeNS

1
Grande! Il nostro benchmark su 200 inserti di prova alla volta con 15 colonne per inserto generate tra il 4100% e il 10400% di miglioramento a seconda del dispositivo e della memoria interna / esterna. Una performance precedente avrebbe condannato il nostro progetto prima che decollasse.
Frank

da 15 minuti a 45 secondi per 13600 file
DoruChidean

13

La compilazione dell'istruzione sql insert aiuta a velocizzare le cose. Può anche richiedere uno sforzo maggiore per puntellare tutto e prevenire possibili iniezioni poiché ora è tutto sulle tue spalle.

Un altro approccio che può anche velocizzare le cose è la classe android.database.DatabaseUtils.InsertHelper, poco documentata. La mia comprensione è che in realtà racchiude le istruzioni di inserimento compilate. Il passaggio da inserti transati non compilati a inserti transati compilati è stato di circa 3 volte il guadagno di velocità (da 2 ms per inserimento a .6 ms per inserimento) per i miei inserti SQLite grandi (oltre 200.000) ma semplici.

Codice di esempio:

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

e come aggiungere ContentValue in iHelp.bind (first_index, thing_1); ?
Vasil Valchev

3

Se la tabella contiene un indice, considera di rilasciarlo prima di inserire i record e quindi di aggiungerlo nuovamente dopo aver eseguito il commit dei record.


1

Se si utilizza un 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;

}

Quindi la funzione privata per eseguire l'inserimento (sempre all'interno del tuo provider di contenuti):

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


    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.