2010-09-24 39 views
13

A menudo escribo pequeños scripts de Python para iterar a través de todas las filas de una tabla DB. Por ejemplo, enviando un correo electrónico a todos los suscriptores.La mejor manera de iterar a través de todas las filas en una tabla DB

que hacerlo de esta manera

conn = MySQLdb.connect(host = hst, user = usr, passwd = pw, db = db) 
cursor = conn.cursor() 
subscribers = cursor.execute("SELECT * FROM tbl_subscriber;") 

for subscriber in subscribers: 
... 

conn.close() 

Me pregunto si hay una mejor manera de hacer esto causa es posible que mis cargas de código de miles de filas en la memoria.

Pensé que podría hacerse mejor con LIMIT. Tal vez algo así:

"SELECT * FROM tbl_subscriber LIMIT %d,%d;" % (actualLimit,steps)  

Cuál es la mejor manera de hacerlo? ¿Cómo lo harías?

Respuesta

32

a menos que tenga BLOB allí, miles de filas no deberían ser un problema. ¿Sabes que es?

Además, ¿por vergüenza para usted y toda su familia haciendo algo como

"SELECT * FROM tbl_subscriber LIMIT %d,%d;" % (actualLimit,steps) 

cuando el cursor hará que la sustitución para que de una manera que evite la inyección de SQL?

c.execute("SELECT * FROM tbl_subscriber LIMIT %i,%i;", (actualLimit,steps)) 
+1

+1 Sane consejos que me pasé por alto – pyfunc

+9

@pyfunc. He escrito mucho PHP en mi vida. No puedo ver el código inseguro sin encogerme. – aaronasterling

+1

¡Gracias por tu consejo! No sabía que ejecutar (...) es capaz de evitar la inyección de SQL. Sin embargo, es un script para uso local. – OemerA

2

En primer lugar tal vez usted no necesita Select * from ...

tal vez es suficiente para ti, para simplemente algunas cosas como: "SELECT correo electrónico de ..."

que disminuiría la cantidad de uso de memoria de todos modos :)

+0

Gran punto - 'SELECT *' Considerado dañino, o al menos perezoso. – Piskvor

1

¿Tiene problemas de memoria? Cuando se itera sobre un cursor, los resultados se obtienen de uno en uno (la implementación de su DB-API puede decidir captar previamente los resultados, pero luego puede ofrecer una función para establecer el número de resultados captados previamente).

6

La mayoría de los conectores MySQL basados ​​en libmysqlclient almacenarán todos los resultados en la memoria del cliente de forma predeterminada por razones de rendimiento (asumiendo que no leerá grandes conjuntos de resultados).

Cuando necesite leer un resultado grande en MySQLdb puede usar un SSCursor para evitar almacenar en búfer conjuntos de resultados grandes y completos.

http://mysql-python.sourceforge.net/MySQLdb.html#using-and-extending

SSCursor - A "server-side" cursor. Like Cursor but uses CursorUseResultMixIn. Use only if you are dealing with potentially large result sets.

Esto sí que introduce complicaciones que hay que tener cuidado. Si usted no lee todos los resultados desde el cursor, una segunda consulta generará un ProgrammingError:

>>> import MySQLdb 
>>> import MySQLdb.cursors 
>>> conn = MySQLdb.connect(read_default_file='~/.my.cnf') 
>>> curs = conn.cursor(MySQLdb.cursors.SSCursor) 
>>> curs.execute('SELECT * FROM big_table') 
18446744073709551615L 
>>> curs.fetchone() 
(1L, '2c57b425f0de896fcf5b2e2f28c93f66') 
>>> curs.execute('SELECT NOW()') 
Traceback (most recent call last): 
    File "<stdin>", line 1, in <module> 
    File "/usr/lib64/python2.6/site-packages/MySQLdb/cursors.py", line 173, in execute 
    self.errorhandler(self, exc, value) 
    File "/usr/lib64/python2.6/site-packages/MySQLdb/connections.py", line 36, in defaulterrorhandler 
    raise errorclass, errorvalue 
_mysql_exceptions.ProgrammingError: (2014, "Commands out of sync; you can't run this command now") 

Esto significa que usted tiene que leer siempre todo, desde el cursor (y potencialmente varios conjuntos de resultados) antes de emitir otra - MySQLdb no hará esto por ti.

16

No tiene que modificar la consulta, puede utilizar el método fetchmany de cursores.Aquí es cómo lo hago:

def fetchsome(cursor, some=1000): 
    fetch = cursor.fetchmany 
    while True: 
     rows = fetch(some) 
     if not rows: break 
     for row in rows: 
      yield row 

esta manera se puede "SELECT * FROM tbl_subscriber;" pero solo obtendrá algunos a la vez.

+0

A menos que me equivoque, mysql encontrará todas las filas que satisfacen su consulta, pero no las envíe hasta que pregunte. La consulta inicial de 'SELECT * FROM tbl_subscriber' anterior funcionaría horriblemente para una tabla con miles de millones de filas. 'LIMIT' significa que mysql solo busca las filas que satisfacen su solicitud y deja de buscar. Los documentos de fetchmany no fueron muy útiles, por lo que podría haber algo de magia, pero lo dudo. –

Cuestiones relacionadas