Prestazioni di importazione InnoDB


10

Sto lottando con l'importazione di massa di una tabella InnoDB piuttosto grande composta da circa 10 milioni di righe (o 7 GB) (che per me è il tavolo più grande con cui ho lavorato finora).

Ho fatto alcune ricerche su come migliorare la velocità di importazione di Inno e per il momento la mia configurazione è simile a questa:

/etc/mysql/my.cnf/
[...]
innodb_buffer_pool_size = 7446915072 # ~90% of memory
innodb_read_io_threads = 64
innodb_write_io_threads = 64
innodb_io_capacity = 5000
innodb_thread_concurrency=0
innodb_doublewrite = 0
innodb_log_file_size = 1G
log-bin = ""
innodb_autoinc_lock_mode = 2
innodb_flush_method = O_DIRECT
innodb_flush_log_at_trx_commit=2
innodb_buffer_pool_instances=8


import is done via bash script, here is the mysql code:
SET GLOBAL sync_binlog = 1;
SET sql_log_bin = 0;
SET FOREIGN_KEY_CHECKS = 0;
SET UNIQUE_CHECKS = 0;
SET AUTOCOMMIT = 0;
SET SESSION tx_isolation='READ-UNCOMMITTED';
LOAD DATA LOCAL INFILE '$filepath' INTO TABLE monster
COMMIT;

I dati sono forniti in un CSVfile.
Attualmente collaudo le mie impostazioni con piccoli "dump di test" con 2 milioni, 3 milioni, ... righe ciascuno e li utilizzo time import_script.shper confrontare le prestazioni.

Lo svantaggio è che ho solo un tempo di esecuzione complessivo, quindi devo attendere il completamento dell'importazione completa per ottenere un risultato.

I miei risultati finora:

  • 10.000 righe: <1 secondo
  • 100000 righe: 10 secondi
  • 300000 file: 40 secondi
  • 2 milioni di file: 18 minuti
  • 3 milioni di file: 26 minuti
  • 4 milioni di righe: (cancellato dopo 2 ore)

Sembra che non ci sia una soluzione per "libri di cucina" e si deve capire da soli il mix ottimale di impostazioni.
Oltre ai suggerimenti su cosa cambiare nella mia configurazione, apprezzerei molto più informazioni su come potrei valutare meglio il processo di importazione / ottenere maggiori informazioni su ciò che sta accadendo e su dove potrebbe essere il collo di bottiglia.
Ho provato a leggere la documentazione per le impostazioni che sto cambiando, ma ancora una volta non sono a conoscenza di effetti collaterali e se potrei anche ridurre le prestazioni con un valore scelto male.

Per il momento, vorrei provare un suggerimento dalla chat da utilizzare MyISAMdurante l'importazione e successivamente cambiare il motore della tabella.
Vorrei provare questo, ma per il momento la mia DROP TABLEquery richiede anche ore per terminare. (Il che sembra un altro indicatore, la mia impostazione è inferiore all'ottimale).

Ulteriori informazioni:
la macchina che sto utilizzando ha 8 GB di RAM e un disco rigido ibrido a stato solido con 5400 giri / min.
Mentre miriamo anche a rimuovere i dati obsoleti dalla tabella in questione, ho ancora bisogno di un'importazione piuttosto veloce in
a) test automatic data cleanup featuredurante lo sviluppo
eb) nel caso in cui il nostro server si arresti in modo anomalo vorremmo usare il nostro secondo server come sostituto (che richiede dati aggiornati, l'ultima importazione ha richiesto più di 24 ore)

mysql> SHOW CREATE TABLE monster\G
*************************** 1. row ***************************
       Table: monster
Create Table: CREATE TABLE `monster` (
  `monster_id` int(11) NOT NULL AUTO_INCREMENT,
  `ext_monster_id` int(11) NOT NULL DEFAULT '0',
  `some_id` int(11) NOT NULL DEFAULT '0',
  `email` varchar(250) NOT NULL,
  `name` varchar(100) NOT NULL,
  `address` varchar(100) NOT NULL,
  `postcode` varchar(20) NOT NULL,
  `city` varchar(100) NOT NULL,
  `country` int(11) NOT NULL DEFAULT '0',
  `address_hash` varchar(250) NOT NULL,
  `lon` float(10,6) NOT NULL,
  `lat` float(10,6) NOT NULL,
  `ip_address` varchar(40) NOT NULL,
  `cookie` int(11) NOT NULL DEFAULT '0',
  `party_id` int(11) NOT NULL,
  `status` int(11) NOT NULL DEFAULT '2',
  `creation_date` datetime NOT NULL,
  `someflag` tinyint(1) NOT NULL DEFAULT '0',
  `someflag2` tinyint(4) NOT NULL,
  `upload_id` int(11) NOT NULL DEFAULT '0',
  `news1` tinyint(4) NOT NULL DEFAULT '0',
  `news2` tinyint(4) NOT NULL,
  `someother_id` int(11) NOT NULL DEFAULT '0',
  `note` varchar(2500) NOT NULL,
  `referer` text NOT NULL,
  `subscription` int(11) DEFAULT '0',
  `hash` varchar(32) DEFAULT NULL,
  `thumbs1` int(11) NOT NULL DEFAULT '0',
  `thumbs2` int(11) NOT NULL DEFAULT '0',
  `thumbs3` int(11) NOT NULL DEFAULT '0',
  `neighbours` tinyint(4) NOT NULL DEFAULT '0',
  `relevance` int(11) NOT NULL,
  PRIMARY KEY (`monster_id`),
  KEY `party_id` (`party_id`),
  KEY `creation_date` (`creation_date`),
  KEY `email` (`email`(4)),
  KEY `hash` (`hash`(8)),
  KEY `address_hash` (`address_hash`(8)),
  KEY `thumbs3` (`thumbs3`),
  KEY `ext_monster_id` (`ext_monster_id`),
  KEY `status` (`status`),
  KEY `note` (`note`(4)),
  KEY `postcode` (`postcode`),
  KEY `some_id` (`some_id`),
  KEY `cookie` (`cookie`),
  KEY `party_id_2` (`party_id`,`status`)
) ENGINE=InnoDB AUTO_INCREMENT=13763891 DEFAULT CHARSET=utf8

2
Hai provato con importazioni meno grandi, come file da 10K o 100K?
ypercubeᵀᴹ

1
Si prega di eseguire SHOW CREATE TABLE yourtable\Gper mostrarci la struttura della tabella di questa tabella di 10 milioni di righe.
RolandoMySQLDBA,

@RolandoMySQLDBA, quindi l'ho fatto (con nomi di campo oscurati)
nuala,

Disabilitando il doppio buffer di scrittura ( innodb_doublewrite = 0), l'installazione di MySQL non è protetta dagli arresti anomali: se si verifica un'interruzione dell'alimentazione (non un arresto anomalo di MySQL), i dati potrebbero essere danneggiati in modo invisibile.
jfg956,

Risposte:


13

Innanzitutto, è necessario sapere cosa si sta facendo in InnoDB quando si inseriscono milioni di righe in una tabella InnoDB. Diamo un'occhiata all'architettura InnoDB.

Architettura di InnoDB

Nell'angolo in alto a sinistra, c'è un'illustrazione del pool di buffer InnoDB. Si noti che esiste una sezione dedicata al buffer di inserimento. Che cosa fa? Viene utilizzato per migrare le modifiche agli indici secondari dal pool di buffer al buffer di inserimento all'interno del tablespace di sistema (aka ibdata1). Per impostazione predefinita, innodb_change_buffer_max_size è impostato su 25. Ciò significa che è possibile utilizzare fino al 25% del pool di buffer per l'elaborazione di indici secondari.

Nel tuo caso, hai 6.935 GB per il pool di buffer InnoDB. Per l'elaborazione degli indici secondari verranno utilizzati un massimo di 1.734 GB.

Ora guarda il tuo tavolo. Hai 13 indici secondari. Ogni riga elaborata deve generare una voce di indice secondaria, associarla alla chiave primaria della riga e inviarla come coppia dal buffer di inserimento nel pool di buffer nel buffer di inserimento in ibdata1. Ciò accade 13 volte con ogni riga. Moltiplicalo per 10 milioni e puoi quasi sentire arrivare un collo di bottiglia.

Non dimenticare che l'importazione di 10 milioni di righe in una singola transazione accumulerà tutto in un segmento di rollback e riempirà lo spazio UNDO in ibdata1.

SUGGERIMENTI

SUGGERIMENTO # 1

Il mio primo suggerimento per l'importazione di questa tabella piuttosto grande sarebbe

  • Elimina tutti gli indici non univoci
  • Importa i dati
  • Crea tutti gli indici non univoci

SUGGERIMENTO # 2

Sbarazzarsi di indici duplicati. Nel tuo caso, hai

KEY `party_id` (`party_id`),
KEY `party_id_2` (`party_id`,`status`)

Entrambi gli indici iniziano con party_id, è possibile aumentare l'elaborazione dell'indice secondario di almeno il 7,6% eliminando un indice su 13. È necessario eseguire infine

ALTER TABLE monster DROP INDEX party_id;

SUGGERIMENTO # 3

Sbarazzati degli indici che non usi. Controlla il codice dell'applicazione e verifica se le tue query utilizzano tutti gli indici. Potresti voler esaminare l' utilizzo di pt-index per fargli suggerire quali indici non vengono utilizzati.

SUGGERIMENTO # 4

È necessario aumentare innodb_log_buffer_size a 64M poiché l'impostazione predefinita è 8M. Un buffer di registro più grande può aumentare le prestazioni di I / O di scrittura di InnoDB.

EPILOGO

Mettendo in atto i primi due suggerimenti, procedi come segue:

  • Elimina i 13 indici non univoci
  • Importa i dati
  • Crea tutti gli indici non univoci tranne l' party_idindice

Forse quanto segue può aiutare

CREATE TABLE monster_new LIKE monster;
ALTER TABLE monster_new
  DROP INDEX `party_id`,
  DROP INDEX `creation_date`,
  DROP INDEX `email`,
  DROP INDEX `hash`,
  DROP INDEX `address_hash`,
  DROP INDEX `thumbs3`,
  DROP INDEX `ext_monster_id`,
  DROP INDEX `status`,
  DROP INDEX `note`,
  DROP INDEX `postcode`,
  DROP INDEX `some_id`,
  DROP INDEX `cookie`,
  DROP INDEX `party_id_2`;
ALTER TABLE monster RENAME monster_old;
ALTER TABLE monster_new RENAME monster;

Importa i dati in monster. Quindi, esegui questo

ALTER TABLE monster
  ADD INDEX `creation_date`,
  ADD INDEX `email` (`email`(4)),
  ADD INDEX `hash` (`hash`(8)),
  ADD INDEX `address_hash` (`address_hash`(8)),
  ADD INDEX `thumbs3` (`thumbs3`),
  ADD INDEX `ext_monster_id` (`ext_monster_id`),
  ADD INDEX `status` (`status`),
  ADD INDEX `note` (`note`(4)),
  ADD INDEX `postcode` (`postcode`),
  ADD INDEX `some_id` (`some_id`),
  ADD INDEX `cookie` (`cookie`),
  ADD INDEX `party_id_2` (`party_id`,`status`);

PROVACI !!!

ALTERNATIVA

È possibile creare una tabella chiamata monster_csvcome tabella MyISAM senza indici e procedere come segue:

CREATE TABLE monster_csv ENGINE=MyISAM AS SELECT * FROM monster WHERE 1=2;
ALTER TABLE monster RENAME monster_old;
CREATE TABLE monster LIKE monster_old;
ALTER TABLE monster DROP INDEX `party_id`;

Importa i tuoi dati in monster_csv. Quindi, utilizzare mysqldump per creare un'altra importazione

mysqldump -t -uroot -p mydb monster_csv | sed 's/monster_csv/monster/g' > data.sql

Il file mysqldump data.sqlestenderà i comandi INSERT importando 10.000-20.000 righe alla volta.

Ora carica il mysqldump

mysql -uroot -p mydb < data.sql

Infine, elimina la tabella MyISAM

DROP TABLE monster_csv;

Non ero nemmeno a conoscenza di tutte quelle chiavi (non è il mio design) ma la tua spiegazione sembra molto convincente. Per oggi è troppo tardi per iniziare un altro tentativo, ma vedo alcuni ottimi consigli su cosa provare domani. Ti terremo informato! <3
nuala il

1
Sono riuscito a importare l'intero database (non solo la monstertabella) in meno di 20 minuti quando non ho chiavi sulle tabelle InnoDB. L'aggiunta di chiavi ha richiesto ca. altri 20 min. Direi che questo risolve praticamente il mio problema in questo caso. Grazie mille!
nuala

8

Volevo scrivere un commento (poiché questa non è una risposta definitiva), ma è diventato troppo lungo:

Ho intenzione di darti diversi consigli e possiamo entrare nei dettagli per ognuno, se vuoi:

  • Riduci la durata (ne hai già fatto parte). Le ultime versioni consentono anche di fare di più. Puoi andare fino alla disabilitazione del doppio buffer di scrittura, poiché la corruzione non è un problema per le importazioni.
  • Aumenta buffering di: aumenta la dimensione del registro delle transazioni e aumenta la dimensione del pool di buffer disponibile. Monitorare l'utilizzo e i punti di controllo del file di registro delle transazioni. Non temere log enormi per un'importazione.
  • Evita enormi transazioni: il tuo rollback diventerà pieno di dati non necessari. Questo è probabilmente il tuo problema più grande.
  • SQL sarà un collo di bottiglia, eviterà il sovraccarico SQL (handlersocket, memcached) e / o caricarlo in concomitanza con più thread contemporaneamente. La concorrenza deve raggiungere un punto debole, non troppo, non troppo poco.
  • Caricare i dati nella frammentazione dell'ordine delle chiavi primarie può essere un'isola
  • Prova la compressione InnoDB se IO è il tuo collo di bottiglia e la CPU e la memoria non rallentano
  • Prova a creare le tue chiavi secondarie in seguito (più veloce in alcuni casi), non caricare i dati indicizzati - DISABLE KEYS non influisce su InnoDB . In caso contrario, monitorare il buffer di inserimento (magari sorpassando metà del pool di buffer).
  • Modificare o disabilitare l'algoritmo di checksum - probabilmente non è un problema, ma diventa un collo di bottiglia nelle schede flash di fascia alta.
  • Ultima risorsa: monitora il tuo server per trovare il collo di bottiglia attuale e prova a mitigarlo (InnoDB è molto flessibile al riguardo).

Ricordare che alcuni di questi non sono sicuri o consigliabili per le non importazioni (funzionamento normale).


Grazie mille! Mi piace provare prima l' idea di Rolando riguardo agli indici, ma immagino che questo "rollback delle transazioni" continuerà a essere un problema. Potresti approfondire questo? Penso di voler disabilitare quanta più funzionalità possibile durante l'importazione e riattivare quando si va in produzione ~ Penso ...
nuala

1
Il suggerimento di Rolando è il mio punto 7. Evitare il sovraccarico del rollback è facile come una combinazione di SET SESSION tx_isolation='READ-UNCOMMITTED';(utile solo se si importa con più thread in parallelo) e il commento @ypercube sull'inserimento in batch. Hai un esempio completo qui: mysqlperformanceblog.com/2008/07/03/… Assicurati di sfruttare tutte le funzionalità delle ultime versioni di InnoDB: mysqlperformanceblog.com/2011/01/07/…
jynus

1
Ho avuto l'impressione generale che uno evitasse di importare in mandrini più piccoli, ma preferirei un'operazione "tutto compreso", ma vedo che il multi-threading potrebbe aprire alcune possibilità. Immagino sia molto specifico. Tuttavia ho accettato la risposta di Rolando in quanto questo tweak (il tuo # 7) da solo mi ha aiutato a ottenere l'importazione completa in <1 ora ma la tua lista è decisamente tutt'altro che inutile e immagino che la userò come riferimento non appena il tasso che il nostro DB sta crescendo in qualche modo mi spaventa :)
nuala, il

Sono d'accordo con @yoshi. La tua risposta è più completa in termini di risoluzione dei problemi e miglioramenti delle prestazioni. +1
RolandoMySQLDBA

3

La maggior parte dei buoni consigli è stata data finora, ma senza molte spiegazioni per i migliori. Darò maggiori dettagli.

Innanzitutto, ritardare la creazione dell'indice è buono, con sufficienti dettagli in altre risposte. Non ci tornerò.

Un file di registro InnoDB più grande ti aiuterà molto (se stai usando MySQL 5.6 in quanto non è possibile aumentarlo in MySQL 5.5). Stai inserendo 7 GB di dati, consiglierei una dimensione totale del registro di almeno 8 GB (mantieni il innodb_log_files_in_groupsuo valore predefinito (2) e bump innodb_log_file_sizea 4 GB). Questo 8 GB non è esatto: dovrebbe essere almeno la dimensione di importazione nel registro REDO e probabilmente raddoppiare o quadruplicare quella dimensione. Il ragionamento alla base della dimensione del registro di InnoDB aumenta il fatto che quando il registro sarà quasi pieno, InnoDB inizierà a scaricare in modo aggressivo il suo pool di buffer su disco per evitare il riempimento del registro (quando il registro è pieno, InnoDB non può scrivere alcun database fino a quando alcuni le pagine del pool di buffer vengono scritte su disco).

Un file di registro InnoDB più grande ti aiuterà, ma dovresti anche inserirlo nell'ordine delle chiavi primarie (ordina il tuo file prima di inserirlo). Se si inserisce nell'ordine della chiave primaria, InnoDB riempirà una pagina, quindi un'altra e così via. Se non si inserisce nell'ordine delle chiavi primarie, l'inserimento successivo potrebbe finire in una pagina piena e comporterà una "suddivisione della pagina". Questa suddivisione della pagina sarà costosa per InnoDB e rallenterà l'importazione.

Hai già un pool di buffer grande quanto la tua RAM ti consente e se il tuo tavolo non si adatta ad esso, non c'è molto che puoi fare se non acquistare più RAM. Ma se la tabella si adatta al pool di buffer ma è maggiore del 75% del pool di buffer, potresti provare ad aumentare innodb_max_dirty_pages_pcta 85 o 95 durante l'importazione (il valore predefinito è 75). Questo parametro di configurazione indica a InnoDB di iniziare lo svuotamento aggressivo del pool di buffer quando la percentuale di pagine sporche raggiunge questo limite. Aumentando questo parametro (e se si è fortunati nella dimensione dei dati), è possibile evitare IO aggressivi durante l'importazione e ritardare tali IO in un secondo momento.

Forse (e questa è una supposizione) l'importazione dei tuoi dati in molte piccole transazioni ti aiuterà. Non so esattamente come sia costruito il registro REDO, ma se è bufferizzato nella RAM (e nel disco quando sarebbe necessaria troppa RAM) mentre la transazione sta facendo progressi, potresti finire con IO inutili. Puoi provare questo: una volta ordinato il file, suddividilo in molti blocchi (prova con 16 MB e altre dimensioni) e importali uno per uno. Ciò consentirebbe anche di controllare l'avanzamento dell'importazione. Se non si desidera che i dati siano parzialmente visibili ad altri lettori mentre si esegue l'importazione, è possibile importare utilizzando un nome di tabella diverso, creare gli indici in un secondo momento, quindi rinominare la tabella.

A proposito del tuo disco ibrido SSD / 5400RPM, non ne so nulla e come ottimizzarlo. 5400 RPM sembra lento per un database, ma forse l'SSD lo sta evitando. Forse stai riempiendo la parte SSD del tuo disco di scritture sequenziali nel registro REDO e l'SSD sta danneggiando le prestazioni. Non lo so.

Un brutto consiglio che non dovresti provare (o fare attenzione) è il seguente: non usare il multi-thread: sarà molto difficile ottimizzare per evitare la divisione delle pagine in InnoDB. Se si desidera utilizzare il multi-thread, inserire in tabelle diverse (o in partizioni diverse della stessa tabella).

Se stai considerando il multi-thread, forse hai un computer multi-socket (NUMA). In questo caso, assicurati di evitare il problema di follia di scambio di MySQL .

Se si utilizza MySQL 5.5, eseguire l'aggiornamento a MySQL 5.6: ha la possibilità di aumentare la dimensione del registro REDO e ha algoritmi di svuotamento del pool di buffer migliori.

Buona fortuna con la tua importazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.