91 votes

Base de données SQLite Android : insertion lente

J'ai besoin d'analyser un fichier XML assez volumineux (entre une centaine et plusieurs centaines de kilooctets), ce que je fais en utilisant Xml#parse(String, ContentHandler) . Je suis en train de le tester avec un fichier de 152KB.

Pendant l'analyse syntaxique, j'insère également les données dans une base de données SQLite en utilisant des appels similaires à ce qui suit : getWritableDatabase().insert(TABLE_NAME, "_id", values) . L'ensemble de ces opérations prend environ 80 secondes pour le fichier de test de 152 Ko (ce qui revient à insérer environ 200 lignes).

Lorsque je mets en commentaire toutes les déclarations d'insertion (mais que je laisse tout le reste, comme la création de ContentValues etc.) le même fichier ne prend que 23 secondes.

Est-il normal que les opérations de la base de données aient une telle surcharge ? Puis-je faire quelque chose à ce sujet ?

189voto

WarrenFaith Points 28137

Vous devriez faire des insertions par lots.

Pseudocode :

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

Cela a considérablement augmenté la vitesse des insertions dans mes applications.

Mise à jour :
@Yuku a fourni un billet de blog très intéressant : Android utilisant inserthelper pour des insertions plus rapides dans une base de données sqlite

68voto

qefzec Points 253

Puisque l'InsertHelper mentionné par Yuku et Brett est déprécié maintenant (niveau 17 de l'API), il semble que la bonne alternative recommandée par Google soit l'utilisation de Déclaration SQLite .

J'ai utilisé la méthode d'insertion dans la base de données comme ceci :

database.insert(table, null, values);

Après avoir également rencontré de sérieux problèmes de performances, le code suivant a accéléré mes 500 insertions de 14,5 secondes à seulement 270 ms incroyable !

Voici comment j'ai utilisé 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();
}

13voto

Brett Points 83

La compilation de l'instruction d'insertion sql permet d'accélérer les choses. Mais cela peut aussi demander plus d'efforts pour tout consolider et éviter toute injection possible, puisque tout repose maintenant sur vos épaules.

Une autre approche qui peut également accélérer les choses est la classe sous-documentée Android.database.DatabaseUtils.InsertHelper. D'après ce que j'ai compris, elle enveloppe les instructions d'insertion compilées. Passer des insertions transactées non compilées aux insertions transactées compilées a permis de gagner environ 3 fois la vitesse (2 ms par insertion à 0,6 ms par insertion) pour mes insertions SQLite importantes (200 000 entrées et plus) mais simples.

Exemple de code :

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

3voto

kaD'argo Points 25

Si la table comporte un index, envisagez de le supprimer avant d'insérer les enregistrements, puis de le réinsérer après avoir validé les enregistrements.

1voto

CircuitBreaker716 Points 363

Si vous utilisez 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;

}

Puis la fonction privée pour effectuer l'insertion (toujours dans votre fournisseur de contenu) :

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

    }

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X