Base de datos SQLite de Android: inserción lenta

Necesito analizar un archivo XML bastante grande (que varía entre aproximadamente cien kilobytes y varios cientos de kilobytes), lo cual estoy haciendo usando Xml#parse(String, ContentHandler) . Actualmente estoy probando esto con un archivo de 152 KB.

Durante el análisis, también inserto los datos en una base de datos SQLite utilizando llamadas similares a las siguientes: getWritableDatabase().insert(TABLE_NAME, "_id", values) . Todo esto en conjunto toma alrededor de 80 segundos para el archivo de prueba de 152 KB (que se reduce a la inserción de aproximadamente 200 filas).

Cuando comento todas las instrucciones de inserción (pero dejo todo lo demás, como crear ContentValues etc.), el mismo archivo solo demora 23 segundos.

¿Es normal que las operaciones de la base de datos tengan una sobrecarga tan grande? ¿Puedo hacer algo al respecto?

Deberías hacer inserciones por lotes.

Pseudocódigo:

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

Eso incrementó la velocidad de las inserciones en mis aplicaciones extremadamente.

Actualizar:
@Yuku proporcionó una publicación de blog muy interesante: Android utilizando inserthelper para realizar inserciones más rápidas en la base de datos sqlite

Dado que el InsertHelper mencionado por Yuku y Brett está en desuso ahora (API nivel 17), parece que la alternativa correcta recomendada por Google es el uso de SQLiteStatement .

Usé el método de inserción de base de datos así:

 database.insert(table, null, values); 

Después de que también experimenté algunos problemas graves de rendimiento, el siguiente código aceleró mis 500 insertos de 14.5 segundos a solo 270 ms , ¡increíble!

Aquí es cómo utilicé 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(); } 

La comstackción de la instrucción de inserción sql ayuda a acelerar las cosas. También puede requerir más esfuerzo para apuntalar todo y evitar una posible inyección, ya que ahora todo recae sobre sus hombros.

Otro enfoque que también puede acelerar las cosas es la clase de base de datos android.database.DatabaseUtils.InsertHelper. Mi entendimiento es que realmente envuelve las declaraciones de comstackción insertadas. Pasar de las inserciones transaccionadas no comstackdas a las inserciones transaccionadas comstackdas fue aproximadamente una ganancia de velocidad de 3x (2ms por inserción a .6ms por inserción) para mis inserciones grandes (200K + entradas) pero simples de SQLite.

Código de muestra:

 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 

Si la tabla tiene un índice, considere descartarla antes de insertar los registros y luego volver a agregarla después de que haya confirmado sus registros.

Si usa 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; } 

Luego, la función privada para realizar la inserción (aún dentro de su proveedor de contenido):

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