2010-10-15 16 views
12

Sto sviluppando un programma in Python che accede a un database MySQL usando MySQLdb. In determinate situazioni, devo eseguire un comando INSERT o REPLACE su molte righe. Attualmente sto facendo così:Perché eseguirlo lentamente in Python MySQLdb?

db.execute("REPLACE INTO " + table + " (" + ",".join(cols) + ") VALUES" + 
    ",".join(["(" + ",".join(["%s"] * len(cols)) + ")"] * len(data)), 
    [row[col] for row in data for col in cols]) 

Funziona bene, ma è un po 'imbarazzante. Mi stavo chiedendo se potevo rendere più facile la lettura, e ho scoperto il comando executemany. Ho cambiato il mio codice in questo modo:

db.executemany("REPLACE INTO " + table + " (" + ",".join(cols) + ") " + 
    "VALUES(" + ",".join(["%s"] * len(cols)) + ")", 
    [tuple(row[col] for col in cols) for row in data]) 

Ha funzionato, ma ha funzionato molto più lentamente. Nei miei test, per insiemi di dati relativamente piccoli (circa 100-200 righe), ha funzionato circa 6 volte più lentamente. Per i big data set (circa 13.000 file, il più grande che mi aspetto di gestire), ha funzionato circa 50 volte più lentamente. Perché sta facendo questo?

Mi piacerebbe davvero semplificare il mio codice, ma non voglio il grande calo delle prestazioni. Qualcuno sa di un modo per renderlo più veloce?

Sto usando Python 2.7 e MySQLdb 1.2.3. Ho provato ad armeggiare con la funzione setinputsizes, ma non sembrava che facesse nulla. Ho guardato il codice sorgente MySQLdb e sembra che non dovrebbe fare nulla.

+0

quante righe stai inserendo/sostituendo? la tua seconda affermazione crea un enorme elenco in memoria prima di inviarlo a mysql. – nosklo

+1

Sto sostituendo fino a 13.000 righe. Non penso che la creazione della lista sia il collo di bottiglia. Se creo l'elenco ma non lo passo al cursore db, ci vuole appena un po 'di tempo. –

+0

(Non risponderà alla domanda, ma ...) 'INSERIRE ... ON DUPLICATE KEY UPDATE ...' è quasi sempre meglio di 'REPLACE ...'. –

risposta

19

Provare a scrivere la parola "valori" nella query: questo sembra essere un bug/regressione in MySQL-python 1.2.3.

L'implementazione di executemany() di MySQL-python corrisponde alla clausola VALUES con un'espressione regolare e quindi clona semplicemente l'elenco di valori per ogni riga di dati, quindi si finisce per eseguire esattamente la stessa query del primo approccio.

Sfortunatamente l'espressione regolare ha perso il flag senza distinzione tra maiuscole e minuscole in quella versione (successivamente fissata nel trunk r622 ma non è mai stata sostituita al ramo 1.2), quindi si degrada per iterare sui dati e sparare una query per riga.

+0

L'ho provato e funziona! Con "valori" in lettere minuscole, è altrettanto veloce con executemany che con execute, o talvolta un po 'più veloce. –

+1

Si noti che la regex 1.2.3 non funziona con gli argomenti nelle query ON DUPLICATE KEY UPDATE (l'espressione regolare corrisponde solo ai primi argomenti), quindi i valori di lower-case possono generare confusione (poiché funzionano con execute()) "not tutti gli argomenti convertiti durante la formattazione della stringa "errori. Per evitare, utilizzare il formato VALUES() anziché gli argomenti nella parte ON DUPLICATE KEY della query. –

+0

È stato corretto in [1.2.4] (https://github.com/farcepest/MySQLdb1/blob/MySQLdb-1.2.4/MySQLdb/cursors.py#L43). – saaj

1

Il primo esempio è una singola (grande) istruzione che viene generata e quindi inviata al database.

Il secondo esempio è un'istruzione molto più semplice che inserisce/sostituisce una singola riga ma viene eseguita più volte. Ogni comando viene inviato al database separatamente, quindi devi pagare i tempi di consegna dal client al server e viceversa per ogni riga inserita. Penserei che questa latenza aggiuntiva introdotta tra i comandi sia la ragione principale della diminuzione delle prestazioni del secondo esempio.

+0

Questo è quello che sospettavo.Ho pensato che forse la funzione executemany è abbastanza sofisticata da inviare tutti i comandi in un'unica query, ma non sembra così. –

1

Sconsigliamo fortemente di utilizzare executeMany in pyodbc e ceodbc entrambi lenti e contiene molti bug.

Utilizzare invece lo execute e creare manualmente la query SQL utilizzando un semplice formato stringa.

transaction = "TRANSACTION BEGIN {0} COMMIT TRANSACTION 

bulkRequest = "" 
for i in range(0, 100) 
    bulkRequest = bulkRequest + "INSERT INTO ...... {0} {1} {2}" 

ceodbc.execute(transaction.format(bulkRequest)) 

L'implementazione corrente è molto semplice, veloce e affidabile.

Problemi correlati