web-dev-qa-db-fra.com

Android SQLite: insertion lente

J'ai besoin d'analyser un fichier XML assez volumineux (variant entre une centaine de kilo-octets et plusieurs centaines de kilo-octets), ce que je fais en utilisant Xml#parse(String, ContentHandler). Je teste actuellement cela avec un fichier de 152 Ko.

Pendant l'analyse, j'insère également les données dans une base de données SQLite à l'aide d'appels similaires à ce qui suit: getWritableDatabase().insert(TABLE_NAME, "_id", values). Tout cela prend environ 80 secondes pour le fichier de test de 152 Ko (ce qui revient à insérer environ 200 lignes).

Lorsque je commente toutes les instructions 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 base de données aient un tel surcoût? Puis-je faire quelque chose à ce sujet?

90
benvd

Vous devez 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 article de blog très intéressant: Android utilisant inserthelper pour des insertions plus rapides dans la base de données sqlite

188
WarrenFaith

Étant donné que le InsertHelper mentionné par Yuku et Brett est obsolète maintenant (API niveau 17), il semble que la bonne alternative recommandée par Google utilise SQLiteStatement .

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

database.insert(table, null, values);

Après avoir également rencontré de graves problèmes de performances, le code suivant a accéléré mes 500 insertions de 14,5 s à 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();
}
68
qefzec

La compilation de l'instruction sql insert permet d'accélérer les choses. Cela peut également nécessiter plus d'efforts pour tout consolider et empêcher une éventuelle injection, car tout est désormais sur vos épaules.

Une autre approche qui peut également accélérer les choses est la classe Android.database.DatabaseUtils.InsertHelper, sous-documentée. Je crois comprendre qu'il englobe en fait les instructions d'insertion compilées. Passer des insertions traitées non compilées aux insertions traitées compilées était d'environ un gain de vitesse 3x (2 ms par insertion à 0,6 ms par insertion) pour mes insertions SQLite simples (200K + entrées) 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();
13
Brett

Si la table contient un index, pensez à le supprimer avant d'insérer les enregistrements, puis à le rajouter après avoir validé vos enregistrements.

3
kaD'argo

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;

}

Ensuite, la fonction privée pour effectuer l'insertion (toujours à l'intérieur de 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);


    }
1
CircuitBreaker716