2013-06-02 8 views
5

Devo essere in grado di inserire da una richiesta di modulo 10'000 + riga simile in una volta. Al momento l'ho fatto con una istruzione preparata su una riga ripetuta in loop 10.000 volte dove reindirizzamentoParam ogni var.PHP PDO inserisce più (10000+) stesse righe utilizzando bindParam. Buona pratica?

for ($i=0; $i < intval($cloneCount); $i++) 
{ 
    ... 9 other bindParam 
    $insertG->bindParam(':v1', $v1, PDO::PARAM_STR); 
    $insertG->bindParam(':v2', $v2, PDO::PARAM_INT); 
    $insertG->execute(); 
} 

Ci vogliono quasi 30 secondi per raggiungere e non è certamente una buona pratica. Oggi sono 10'000 ma domani potrebbero arrivare a 100'000.

Se inserisco più righe in una query con (v1,v2),(v1,v2)... ho bisogno di associare ogni valore a un nuovo parametro, quindi credo che avrei bisogno di avere quasi 100'000 bindedParam in una query. Se è UTF-8 e conto circa 2 byte (so che può arrivare a 4) per char la mia query sarà di circa 10 o 20 MB e il server mysql è su un'altra macchina. Dicendo questo sono sorpreso che ci sono voluti solo 30 secondi per la mia richiesta mal progettata per avere successo.

C'è un modo per inviare solo una riga e dire al server mysql di replicare l'ultima riga 10.000 volte?

EDIT soluzione parziale

seguito consigli Bill Karwin e Zsolt Szilagy. Sono riuscito a scendere a 5-6 secondi con le seguenti modifiche di un inserto 10'000 ad un server MySQL remoto:

$dataBase->beginTransaction(); 

$insertG = $dataBase->prepare('INSERT INTO G...) 
... 
10 * bindParam of all kinds 

for ($i=0; $i < 10000; ++$i) 
{ 
    $hashKey = sha1(uniqid().$i); //$hashKey is a binded param 
    $insertG->execute(); 
} 
$dataBase->commit(); 

risposta

12

Non è necessario associareParam() durante ogni iterazione del ciclo. Il bindParam() fa sì che le variabili $ v1, $ v2, ecc. Vengano associate per riferimento, quindi tutto ciò che devi fare è modificare i valori di queste variabili e quindi rieseguire la query. Questo potrebbe ridurre il sovraccarico.

Inoltre è possibile evitare di chiamare intval() ogni volta attraverso il ciclo. Assicurati che $ cloneCount sia forzato al numero intero una volta, prima del ciclo. Questo è un miglioramento molto minore, ma è una buona pratica.

$cloneCount = (int) $cloneCount; 

... 9 other bindParam 
$insertG->bindParam(':v1', $v1, PDO::PARAM_STR); 
$insertG->bindParam(':v2', $v2, PDO::PARAM_INT); 

for ($i=0; $i < $cloneCount; $i++) 
{ 
    $v1 = /* something */ 
    $v2 = /* something */ 
    $insertG->execute(); 
} 

Si dovrebbe anche evitare l'autocommit. Riduci il sovraccarico della transazione di MySQL per l'esecuzione dell'istruzione tramite starting an explicit transaction, inserendo diverse migliaia di righe e quindi eseguendo la transazione.

Tuttavia, il modo migliore per velocizzare l'inserimento in massa di migliaia di righe simili in una singola tabella è utilizzare LOAD DATA LOCAL INFILE anziché INSERT.Questo viene eseguito 10-20 volte più velocemente di INSERT riga per riga, anche se si utilizzano parametri, transazioni, inserimenti su più righe e qualsiasi altro trucco che si possa pensare.

Anche se è necessario utilizzare PHP per scrivere i dati in un file .CSV su disco e quindi utilizzare LOAD DATA INFILE LOCALE su quel file, è ancora molto più veloce.

Vedere anche Speed of INSERT Statements nel manuale MySQL per ulteriori suggerimenti.

+0

Spostare la parte count() fuori dal ciclo è molto più di un piccolo miglioramento, dato che PHP gestisce il conteggio abbastanza lentamente. Buon punto! Un modo normale per farlo è ($ i = 0, $ max = count ($ array); $ i <$ max; $ i ++) –

+0

@ZsoltSzilagy, è vero, ma l'OP non stava facendo count() in il ciclo, stava facendo intval(). Non è così costoso come count(), ma è comunque una chiamata di funzione e ogni piccolo miglioramento è utile se si esegue un ciclo di oltre 100.000 volte. –

+0

Eah hai ragione, avrei dovuto scorrere di nuovo. :) –

4

creare un oggetto wrapper per inserimento di massa.

Vuoi avere qualcosa come $bulkinsert->add($street,$zip); nel tuo ciclo. Dovrebbe costruire internamente una stringa di query con più inserti:

insert into table1 (First,Last) values 
    ("Fred","Smith"), 
    ("John","Smith"), 
    ("Michael","Smith"), 
    ("Robert","Smith") 
    ...; 

vorrei eseguirlo una volta ogni 100 - 1000 chiamate di add(). 500 è un buon compromesso tra la dimensione della query e il tempo di esecuzione. In questo modo risparmi il 99,8% delle query che usi attualmente.

MODIFICA: Come suggerito in un'altra risposta, spostare il conteggio() ot del proprio ciclo. Inoltre, utilizzare ++$i anziché $i++. (Per farla breve, $ i ++ crea un sovraccarico di chiamate di solito da ignorare, ma si è in un ciclo stretto in cui le microottimazioni contano.)

+0

++ $ mi ricorderò! –

+1

Una volta ho chiesto a uno sviluppatore di compilatori la differenza tra i ++ e ++ i. Ha detto inviando questa domanda via email, avevo già sprecato più risorse di calcolo di quanto avrei mai potuto risparmiare scegliendo uno stile di incremento su un altro in tutta la mia carriera. :-) –

+0

@BillKarwin Questo dipende da cosa stai facendo con esso. Come ho già detto, fa solo la differenza in ** loop davvero stretti. E suppongo che quel tipo di compilatore non si sia trasformato in un linguaggio di script. Ma potresti avere ragione che il tuo commento non valeva la pena :-P (senza offesa) –

1

Se ho capito correttamente dalla domanda C'è un modo per inviare solo una riga e dire al server mysql di replicare l'ultima riga 10.000 volte? è necessario replicare la stessa riga più volte.

Per questo, soprattutto se si sta facendo regolarmente, una tabella conteggio (con tanto righe come ci si aspetta di essere il limite pe 100000) e CROSS JOIN possono aiutare a farlo sul lato db e con i set, piuttosto che loop .

Creazione di una tabella conteggio

CREATE TABLE tally(id INT NOT NULL AUTO_INCREMENT PRIMARY KEY); 

DELIMITER $$ 
CREATE PROCEDURE sp_populate_tally(IN n INT) 
BEGIN 
    DECLARE i INT DEFAULT 1; 
    WHILE i <= n DO 
     INSERT INTO tally VALUES (NULL); 
     SET i = i + 1; 
    END WHILE; 
END$$ 
DELIMITER ; 

CALL sp_populate_tally(100000); 

Ora per replicare una fila 10000 volte fanno

INSERT INTO table_name (n1, n2, ...) 
SELECT n1, n2, ... 
    FROM 
(
    SELECT 1 n1, 'TextValue1' n2, ... 
) a CROSS JOIN tally t 
WHERE t.id <= 10000; 

Ecco SQLFidlle demo (AGGIORNATO).

+0

Sì, questa è anche una parte della domanda. Stavo cercando di trovare il modo di farlo ma non riesco a copiare esattamente ogni riga (pensavo di poter modificare ogni riga ma non sarebbe stata efficiente). –

+0

@NicolasManzini Si prega di spiegare la natura delle modifiche. Può essere che possano essere fatti con SQL. – peterm

+0

Ho bisogno di inserire 10.000 righe con contenuto simile, tranne per il fatto che ognuna deve avere un valore diverso per la colonna hashkey e la colonna identificatore che è la colonna primaria autoincrementata primaria. –