Cómo mejorar el rendimiento en SQLite (Android)

Buenas, hace ya un tiempo que vimos como guardar datos en nuestro dispositivo mediante bases de datos SQLite. Ahora he querido retomar este tema porque recientemente he tenido que mejorar el rendimiento de una aplicación sobre todo cuando descargaba datos y los guardaba.





Al contrario de lo que pensaba al principio cuesta mucho más guardar los datos que descargarlos (si no se hace bien). Cuando empece con esto lo que hacía era: descargar los datos de un servicio web, parsear cada uno de los datos y guardarlos por separados.


El proceso de guardar a su vez lo que hacía era leer por la clave principal (PRIMARY KEY) si existía ese registro en la base de datos o no y en función de eso insertaba o actualizaba.


En pequeñas dosis este proceso no sería muy pesado, pero cuando los datos crecen un poco el tiempo de espera crece bastante.


Lo primero que mejore fue la forma en la que decidía si actualizaba o insertaba. Como os he dicho antes primero buscaba is un registro existía y luego hacía un INSERT o un UPDATE, pero existe una forma de hacerlo con una sola instrucción, os pongo un ejemplo:


INSERT OR REPLACE INTO tabla(id, nombre) VALUES (1, ‘nombre’);


Aquí va a ser la propia instrucción la que va a decidir si inserta o actualiza, con lo cual se consigue eliminar la búsqueda previa multiplicada tanta veces como registros insertamos. Debéis tener cuidado de tener definida una clave primaria en vuestra tabla, pues tomará esta como referencia para hacer una acción u otra.


La siguiente mejora consistía en tratar de realizar todas las consultas al mismo tiempo para evitar abrir y cerrar la base de datos un montón de veces. Para eso, lo que hice fue en el objeto que gestiona los accesos a base de datos definí un arraylist donde crear y almacenar todas las consultas para lanzarlas al final del proceso.


En este punto comentaros que si en SQLite tenemos más de una consulta y las lanzamos en una única ejecución solo se ejecuta la primera consulta y el resto son ignoradas. Por lo que tenía que lanzar cada una de las consultas por separado, siendo el tiempo de ejecución todavía bastante considerable.


Pero si a este proceso le añades un inicio de transacción y un fin de transacción, el tiempo mejora considerablemente. En la aplicación que os decía que estaba mejorando he pasado de tener un tiempo de casi 2 minutos para descarga y guardado en base de datos a 3 segundos, lo cual es una mejora bastante notable. Este sería el código de la clase bbdd que maneja los accesos a base de datos:


private ArrayList listSQL;
public void addLibroPoolUpdate(Libro libro){
   if(listSQL == null)
     listSQL = new ArrayList();
   String SQL = "INSERT OR REPLACE INTO libros (id_libro, titulo, isbn) " + "VALUES (“ +libro.getIdLibro() + 
', '" + ReplaceQuote(libro.getTitulo()) + 
"', '" + ReplaceQuote(libro.getISBN()) + 
"');";
   listSQL.add(SQL);
}

public void commitPoolUpdate(){
   if(listSQL != null && listSQL.size() > 0){
      SQLiteDatabase db = getWritableDatabase();
      if(db!=null){
         db.beginTransaction();
         for(int i = 0; i < listSQL.size();i++){
            db.execSQL(listSQL.get(i));
         }
         db.setTransactionSuccessful();
         db.endTransaction();
         db.close();
      }
      listSQL.clear();
   }

}


Al primer método le pasamos el objeto libro y creamos la consulta para insertar ese libro, la cual guardamos en listSQL. El segundo método recorre esa lista para ir ejecutando las consultas con execSQL, antes de ejecutar nada iniciamos una transacción, de esta forma conseguimos que las inserciones se queden en una especie de estado de stand by a la espera de que ejecutemos el método setTransactionSuccessful y el fín de la transacción. De esta forma todo va muchísimo más rápido, ganando en eficacia y dándole al usuario una sensación de rápidez muy agradable.

Es como si estuviéramos ejecutando todas las consultas a la vez, sólo que SQLite no soporta múltiples consultas en un mismo execSQL.


La forma de utilizar estos dos métodos es muy sencillo, donde antes recorríamos un bucle e ibamos guardando en cada una de las vueltas ahora ejecutamos el método addLibroPoolUpdate y al salir del mismo ejecutamos commitPoolUpdate. Os pongo un pequeño ejemplo en el cual se ha leído un objeto JSON y se recorre y parsea el array de objetos del mismo:


ddbb dblibros =  new ddbb(activity);
for(int i = 0; i < librosArray.length(); i++){
   Libro libro = new Libro();
   libro.setIdLibro(librosArray.getJSONObject(i).getInt("id"));
   libro.setTitulo(librosArray.getJSONObject(i).getString("titulo"));
   libro.setISBN(librosArray.getJSONObject(i).getString("isbn"));

   dblibros.addLibroPoolUpdate(libro);
}
dblibros.commitPoolUpdate();
dblibros.close();



Solo os muestro este trozo de código ya que hemos visto como crear un lector de JSON y también  como crear una clase para gestionar base de datos. El objeto Libro es una clase que no hereda de nada y tiene las variables idLibro, titulo e ISBN con sus getter y setter por lo que no entraña ninguna dificultad.


Ahora lo que os queda es que prepareis una gran cantidad de datos y probar a ejecutar el método sin los transaction y con ellos para que veáis la diferencia de tiempo.


Algunos de vosotros venís pidiendo desde hace un tiempo si os puedo incluir el código del artículo en el que creamos el lector de JSON. Pues bien, aprovechando que hago esta revisión de la inserción de datos en SQLite, vamos a incluir un ejemplo bastante completo en el que se descarga un JSON y se guarda en base de datos. Aquí tenéis el archivo para descargar. Únicamente debéis hacer una modificación en la clase JSONParserLibros, indicando la IP o URL donde tenéis alojado vuestro servicio web.


Actualización código del lector JSON
Gracias uno de nuestros lectores que nos alerto de un error en la aplicación el cual impedia que una vez descargado el json se insertara en base de datos. El problema consiste en una comilla sobrante en la instrucción SQL del método addLibroPoolUpdate de la clase bbdd. Os ponemos la instrucción SQL corregida y en rojo la línea que estaba dando el error:




SQL = " INSERT OR REPLACE INTO libros (id_libro, titulo, isbn)  VALUES (" + libro.getIdLibro() + 
", '" + ReplaceQuote(libro.getTitulo()) + 
"', '" + ReplaceQuote(libro.getISBN()) + 
"');";


Comments are closed.