2009-05-11 14 views
11

Ho una tabella di database con un campo stringa univoco e un paio di campi interi. Il campo stringa è in genere lungo 10-100 caratteri.Qual è il modo più efficiente per inserire migliaia di record in una tabella (MySQL, Python, Django)

Una volta ogni minuto ho il seguente scenario: Ricevo un elenco di 2-10 mila tuple corrispondenti alla struttura del record della tabella, ad es.

[("hello", 3, 4), ("cat", 5, 3), ...] 

Ho bisogno di inserire tutte queste tuple al tavolo (ho verificato assumere nessuna di queste stringhe appaiono nel database). Per chiarimenti, sto usando InnoDB, e ho una chiave primaria auto-incrementale per questa tabella, la stringa non è il PK.

Il mio codice attualmente scorre questa lista, per ogni tupla crea un oggetto modulo Python con i valori appropriati, e chiama ".save()", una cosa in questo modo:

@transaction.commit_on_success 
def save_data_elements(input_list): 
    for (s, i1, i2) in input_list: 
     entry = DataElement(string=s, number1=i1, number2=i2) 
     entry.save() 

Questo codice è attualmente uno dei colli di bottiglia delle prestazioni nel mio sistema, quindi sono alla ricerca di modi per ottimizzarlo.

Ad esempio, potrei generare codici SQL contenenti ciascuno un comando INSERT per 100 tuple ("hardcoded" nell'SQL) ed eseguirlo, ma non so se migliorerà qualcosa.

Avete qualche suggerimento per ottimizzare tale processo?

Grazie

+0

Buona domanda! Quindi, le migliori risposte sembrano essere la creazione di un file di testo o la generazione di una query SQL attraverso la concatenazione di stringhe? Questo è un po 'insoddisfacente! – JAL

risposta

11

è possibile scrivere le righe da un file nel formato "field1", "field2", .. e poi utilizzare i dati di carico per caricare loro

data = '\n'.join(','.join('"%s"' % field for field in row) for row in data) 
f= open('data.txt', 'w') 
f.write(data) 
f.close() 

Poi eseguire questo:

LOAD DATA INFILE 'data.txt' INTO TABLE db2.my_table; 

Reference

+0

Dovrebbe essere LOAD DATA INFILE LOCALE a meno che il codice non sia in esecuzione sul server del database. – staticsan

+0

Inoltre, disabilitare gli indici prima del caricamento, quindi abilitarli in seguito (ci vorrà un po 'per creare l'indice). Non ho nemmeno guardato se aiuta con gli inserti di Django. – pufferfish

12

Per MySQL in particolare, il modo più veloce per caricare i dati è utilizzando LOAD DATA INFILE, quindi se si riesce a convertire i dati nel formato che si aspetta, sarà probabilmente è il modo più veloce per farlo entrare in gioco.

+0

L'unico problema potenziale è l'override del metodo save(). Se lo fai, dovrai pensare due volte al tuo progetto. –

+0

@ S.Lott: cosa intendi per "sovrascrivere il save()"? Vuoi dire se sovrascrivo il metodo .save() nella classe del modulo in modo che ci siano attività di pre/post processing che si svolgono mentre si salva attraverso il codice che andrà perso nel "caricamento del file di dati"? Se è così - non è il caso, non sto ignorando .save(). Altrimenti, per favore, elabora ... Grazie –

4

Se non lo fai LOAD DATA INFILE come alcuni degli altri suggerimenti menzionano, due cose che potete fare per accelerare i inserti sono:

  1. Usa istruzioni preparate - questo taglia fuori il sovraccarico di analisi del SQL per ogni inserire
  2. fare tutte le vostre inserti in una singola transazione - questo richiederebbe utilizzando un motore di DB che supporta le transazioni (come InnoDB)
+0

@Sean: Grazie, per "istruzioni preparate" intendi codice SQL con molti elementi% s che "riempio" semplicemente fornendo l'elenco di stringhe/numeri? Inoltre, si prega di dare un'occhiata al mio codice (nel corpo della domanda) - se ho capito bene sto già usando una singola transazione con il decoratore @ transaction.commit_on_success (sto usando InnoDB) –

+0

Non sono davvero certo cosa sta succedendo dietro lo scenese con Django - Sto solo venendo da un background generico di utilizzo di MySQL quindi non so cosa stia facendo riguardo alle transazioni. Per quanto riguarda le istruzioni preparate, sembra che sia un dettaglio di implementazione degli oggetti DataElement. Una dichiarazione preparata sarebbe: 'stmt = Prepare (sqlStatement); stmt.execute (var1, var2 ..) 'piuttosto che' db.execute (sqlStatement, var1, var2 ...) '- è come compilare le espressioni regolari piuttosto che analizzarle ogni volta. –

4

Se riesci a fare una dichiarazione arrotolata a mano INSERT, allora è così che andrei. Una singola istruzione INSERT con clausole a più valori è molto più veloce di molte singole affermazioni INSERT.

+1

@staticsan: Pensi che ci sia un limite "pratico" a tale affermazione? cioè posso inviare al database una singola query INSERT con 10k righe di testo? –

+0

L'unica limitazione reale è la dimensione del buffer di rete. Il valore predefinito di questo era 1Mb per molti anni, ma molte persone lo hanno portato a un massimo di 16Mb. Le versioni più recenti di MySQL sono in grado di supportare anche grandi dimensioni di pacchetti. – staticsan

+1

Si tratta più della dimensione del pacchetto che del numero di record. Man mano che costruisci il tuo buffer di inserimento, non aggiungere altro se così facendo metti il ​​buffer sulla dimensione massima del pacchetto mysql. Farei alcuni benchmark e vedere dove il beneficio inizia a livellarsi. Puoi anche chiedere al tuo server MySQL la dimensione massima del pacchetto: mysql> seleziona @@ max_allowed_packet \ G: @@ max_allowed_packet: 33554432 – Will

1

Questo non è correlato al carico effettivo di dati nel DB, ma ...

Se si fornisce un "Il caricamento dei dati ... Il carico verrà eseguito a breve", il tipo di messaggio all'utente è un'opzione, quindi è possibile eseguire INSERT o DATI CARICO in modo asincrono in un thread diverso.

Solo qualcos'altro da considerare.

+0

Più probabilmente il problema è che il server è così impegnato a elaborare questo input che non può gestire altre richieste . –

+0

Sto già eseguendo l'elaborazione su un thread separato (l'utente non aspetterà che finisca), il mio problema è che a volte il sistema è troppo occupato, quindi c'è una possibilità che la coda si riempia più velocemente della sua pulizia per un tempo sufficiente ora ... –

2

Indipendentemente dal metodo di inserimento, è possibile utilizzare il motore InnoDB per la massima concorrenza di lettura/scrittura. MyISAM bloccherà l'intera tabella per la durata dell'inserto mentre InnoDB (nella maggior parte dei casi) bloccherà solo le righe interessate, consentendo alle istruzioni SELECT di procedere.

+0

Grazie, ho aggiunto un chiarimento sul fatto che sto usando InnoDB –

1

Non so i dettagli esatti, ma è possibile utilizzare la rappresentazione dei dati in stile json e utilizzarli come fixture o qualcosa del genere. Ho visto qualcosa di simile su Django Video Workshop di Douglas Napoleone. Guarda i video allo http://www.linux-magazine.com/online/news/django_video_workshop. e http://www.linux-magazine.com/online/features/django_reloaded_workshop_part_1. Spero che questo aiuti.

Spero che tu possa risolvere il problema. Ho appena iniziato a imparare il Django, quindi posso solo indicarti le risorse.

Problemi correlati