Exportar grandes recordsets de MySQL desde Python

Posted by in MariaDB, MySQL, Python

En la entrada sobre exportación de grandes recordsets ya vimos distintas formas de ejecutar la tarea, y dejamos pendiente cómo conseguirlo usando Python.

Conectando con MySQL desde Python

Ya hablamos de cómo conectar con MySQL desde Python en una entrada anterior, donde también publicaba una clase personalizada mia para facilitarme la tarea. Para este caso, voy a conectarme directamente a MySQL mediante la libreria PyMySQL sin clase ninguna.

Sólo necesito importar la libreria, establecer la información de la conexión, y … listo!


import pymysql

conn = pymysql.connect(host='localhost', port=3306, user='root', passwd='pass', db='')
print ('Conectado!')
conn.close()

Para comprobar el tiempo, me he creado una pequeña clase Cronometro, que nos ayudará:


import time
class Cronometro():
def __init__(self, tarea = ''):
self.__inicio = time.time()
self.tarea = tarea
def iniciar(self):
# Tiempo de inicio de ejecución.
self.__inicio = time.time()

def finalizar(self):
# Devolvemos el tiempo de ejecución.
tmp = time.time()
tmp2 = self.__inicio
self.__inicio = tmp
x = self.tarea + ' --> ' if len(self.tarea) > 0 else ''

return x + str(round(tmp - tmp2,2)) + " segundos"

Cuando hay que recuperar miles y miles de registros

Los clientes de MySQL, cómo PyMySQL, funcionan muy bien para conectar con la base de datos. Normalmente, su funcionamiento consiste en cargar en memoria el resultado de la consulta antes de devolver el control al programa (es decir, cuando termina de trabajar la base de datos). Sin embargo, cuando la cantidad de registros a recuperar es importante, se pierde mucha eficiencia. Aquí explican perfectamente el problema (en inglés), y la solución, que pasa por usar “streaming result set“, mediante el uso de SSCursor.

En el caso de PyMySQL , este tipo de cursos se obtiene mediante:

cur = conn.cursor(pymysql.cursors.SSCursor)

A continuación, es muy importante recuperar registro a registro (fetchone) frente a todos a la vez (fetchall)

Rutina de exportación de datos a CSV

Si vamos a exportar los recordsets a formato CSV, lo principal es usar el paquete CSV. El tutorial más completo para trabajar con dicho paquete es este: http://www.pythonforbeginners.com/systems-programming/using-the-csv-module-in-python/. En nuestro caso, debemos abrir un fichero de escritura, lo vinculamos con el paquete csv, utilizamos csv.writerow para escribir, y una vez terminado, cerrar el fichero.


ofile = open(file_name,'wb')
csv_writer = csv.writer(ofile, delimiter=delimiter, quotechar=quotechar,quoting=quoting)
csv_writer.writerow(datos)
ofile.close()

Es importante configurar las opciones del escritor de CSV, pues son múltiples y variadas.

Antes de continuar, os dejo esta pregunta en StackOverflow sobre cómo guardar un recordset (en este caso usando SQLite) como CSV, donde la primera respuesta da una magnífica pista.

Con estos antecedentes, he construido la siguiente rutina para el proceso de escritura del recordset a CSV es el siguiente:


def write_sql_to_file(file_name, sql, with_header=True, delimiter=',',quotechar='"',quoting=csv.QUOTE_NONNUMERIC, con_sscursor=False):
    cur = conn.cursor(pymysql.cursors.SSCursor) if con_sscursor else conn.cursor()
    cur.execute(sql)
    cabecera= [campo[0] for campo in cur.description]
    ofile = open(file_name,'wb')
    csv_writer = csv.writer(ofile, delimiter=delimiter, quotechar=quotechar,quoting=quoting)
    if with_header:
        csv_writer.writerow(cabecera)
    if con_sscursor:
         while True:
            x = cur.fetchone()
            if x:
                csv_writer.writerow(x)
            else:
                break
    else:
        for x in cur.fetchall():
            csv_writer.writerow(x)
    cur.close()
    ofile.close()

Se ha preparado una consulta para exportar unos cuantos datos, y comprobamos cuanto tarda con SSCursor y sin SScursor, para lo que insertamos el cronómetro en 2 puntos del proceso: en uno medimos lo que tarda la consulta, y en el segundo, lo que tarda la ejecución de la escritura del fichero CSV.

A continuación pongo los resultados obtenidos, para una misma consulta, donde el número de datos ha ido subiendo en cada test.

test 1
Test 1 Test 2 Test 3

En general, acceder a un recordset con SSCursor es más rápido que por la vía normal. Sin embargo, respecto a la escritura del proceso CSV, el recuperar registro a registro es mucho más lento que hacerlo de una sola vez. Aun necesito seguir investigando sobre esto.

Un saludo.

Google+ Comments - Comentarios Google+