Query lente, query inefficienti o query di lunga durata sono problemi che affliggono regolarmente i DBA. Sono sempre onnipresenti, eppure sono una parte inevitabile della vita di chiunque sia responsabile della gestione di un database.
Una cattiva progettazione del database può influire sull'efficienza della query e sulle sue prestazioni. La mancanza di conoscenza o l'uso improprio di chiamate di funzione, stored procedure o routine possono anche causare un degrado delle prestazioni del database e persino danneggiare l'intero cluster di database MySQL.
Per una replica master-slave, una causa molto comune di questi problemi sono le tabelle prive di indici primari o secondari. Ciò causa lo slave lag che può durare a lungo (in uno scenario peggiore).
In questa serie di blog in due parti, ti forniremo un corso di aggiornamento su come affrontare la massimizzazione delle query del database in MySQL per migliorare l'efficienza e le prestazioni.
Aggiungi sempre un indice univoco alla tua tabella
Le tabelle che non hanno chiavi primarie o univoche in genere creano enormi problemi quando i dati diventano più grandi. Quando ciò accade, una semplice modifica dei dati può bloccare il database. La mancanza di indici appropriati e un'istruzione UPDATE o DELETE è stata applicata alla tabella particolare, MySQL sceglierà una scansione completa della tabella come piano di query. Ciò può causare un I/O del disco elevato per letture e scritture e peggiora le prestazioni del database. Vedi un esempio qui sotto:
root[test]> show create table sbtest2\G
*************************** 1. row ***************************
Table: sbtest2
Create Table: CREATE TABLE `sbtest2` (
`id` int(10) unsigned NOT NULL,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT ''
) ENGINE=InnoDB DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest2 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
| 1 | UPDATE | sbtest2 | NULL | ALL | NULL | NULL | NULL | NULL | 1923216 | 100.00 | Using where |
+----+-------------+---------+------------+------+---------------+------+---------+------+---------+----------+-------------+
1 row in set, 1 warning (0.06 sec)
Mentre una tabella con chiave primaria ha un ottimo piano di query,
root[test]> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2097121 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
root[test]> explain extended update sbtest3 set k=52, pad="xx234xh1jdkHdj234" where id=57;
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
| 1 | UPDATE | sbtest3 | NULL | range | PRIMARY | PRIMARY | 4 | const | 1 | 100.00 | Using where |
+----+-------------+---------+------------+-------+---------------+---------+---------+-------+------+----------+-------------+
1 row in set, 1 warning (0.00 sec)
Le chiavi primarie o univoche forniscono un componente vitale per la struttura di una tabella perché questo è molto importante soprattutto quando si esegue la manutenzione su una tabella. Ad esempio, l'utilizzo degli strumenti di Percona Toolkit (come pt-online-schema-change o pt-table-sync) consiglia di disporre di chiavi univoche. Tieni presente che la CHIAVE PRIMARIA è già una chiave univoca e una chiave primaria non può contenere valori NULL ma una chiave univoca. L'assegnazione di un valore NULL a una chiave primaria può causare un errore del tipo,
ERROR 1171 (42000): All parts of a PRIMARY KEY must be NOT NULL; if you need NULL in a key, use UNIQUE instead
Per i nodi slave, è anche comune che in determinate occasioni non sia presente sulla tabella la chiave primaria/univoca che quindi sono discrepanze della struttura della tabella. Puoi usare mysqldiff per ottenere questo risultato oppure puoi mysqldump --no-data … params ed eseguire un diff per confrontare la struttura della sua tabella e verificare se c'è qualche discrepanza.
Scansiona le tabelle con indici duplicati, quindi eliminalo
Anche gli indici duplicati possono causare un degrado delle prestazioni, specialmente quando la tabella contiene un numero enorme di record. MySQL deve eseguire più tentativi per ottimizzare la query ed eseguire più piani di query da controllare. Include la scansione di statistiche o distribuzioni di indici di grandi dimensioni e ciò aggiunge un sovraccarico delle prestazioni in quanto può causare conflitti di memoria o un utilizzo elevato della memoria I/O.
Degrado per le query quando si osservano indici duplicati su una tabella anche gli attributi sulla saturazione del pool di buffer. Ciò può anche influire sulle prestazioni di MySQL quando il checkpoint scarica i log delle transazioni nel disco. Ciò è dovuto all'elaborazione e alla memorizzazione di un indice indesiderato (che in effetti è uno spreco di spazio nel particolare tablespace di quella tabella). Tieni presente che gli indici duplicati vengono archiviati anche nel tablespace, che deve anche essere archiviato nel pool di buffer.
Dai un'occhiata alla tabella seguente che contiene più chiavi duplicate:
root[test]#> show create table sbtest3\G
*************************** 1. row ***************************
Table: sbtest3
Create Table: CREATE TABLE `sbtest3` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`k` int(10) unsigned NOT NULL DEFAULT '0',
`c` char(120) NOT NULL DEFAULT '',
`pad` char(60) NOT NULL DEFAULT '',
PRIMARY KEY (`id`),
KEY `k` (`k`,`pad`,`c`),
KEY `kcp2` (`id`,`k`,`c`,`pad`),
KEY `kcp` (`k`,`c`,`pad`),
KEY `pck` (`pad`,`c`,`id`,`k`)
) ENGINE=InnoDB AUTO_INCREMENT=2048561 DEFAULT CHARSET=latin1
1 row in set (0.00 sec)
e ha una dimensione di 2,3 GiB
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
2.3G /var/lib/mysql/test/sbtest3.ibd
Eliminiamo gli indici duplicati e ricostruiamo la tabella con un alter no-op,
root[test]#> drop index kcp2 on sbtest3; drop index kcp on sbtest3 drop index pck on sbtest3;
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
Query OK, 0 rows affected (0.01 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> alter table sbtest3 engine=innodb;
Query OK, 0 rows affected (28.23 sec)
Records: 0 Duplicates: 0 Warnings: 0
root[test]#> \! du -hs /var/lib/mysql/test/sbtest3.ibd
945M /var/lib/mysql/test/sbtest3.ibd
È stato in grado di risparmiare fino al 59% circa della vecchia dimensione del tablespace, che è davvero enorme.
Per determinare gli indici duplicati, puoi usare pt-duplicate-checker per gestire il lavoro per te.
Migliora il tuo pool di buffer
Per questa sezione mi riferisco solo al motore di archiviazione InnoDB.
Il pool di buffer è un componente importante all'interno dello spazio del kernel di InnoDB. È qui che InnoDB memorizza nella cache i dati della tabella e dell'indice quando si accede. Accelera l'elaborazione perché i dati utilizzati di frequente vengono archiviati nella memoria in modo efficiente utilizzando BTREE. Ad esempio, se disponi di più tabelle composte da>=100GiB e accedi in modo massiccio, ti suggeriamo di delegare una memoria volatile veloce a partire da una dimensione di 128GiB e iniziare ad assegnare il pool di buffer con l'80% della memoria fisica. L'80% deve essere monitorato in modo efficiente. È possibile utilizzare SHOW ENGINE INNODB STATUS \G oppure sfruttare il software di monitoraggio come ClusterControl che offre un monitoraggio dettagliato che include pool di buffer e le relative metriche di integrità. Imposta anche la variabile innodb_buffer_pool_instances di conseguenza. È possibile impostare un valore maggiore di 8 (impostazione predefinita se innodb_buffer_pool_size>=1GiB), ad esempio 16, 24, 32 o 64 o superiore, se necessario.
Durante il monitoraggio del pool di buffer, è necessario controllare la variabile di stato globale Innodb_buffer_pool_pages_free che fornisce informazioni se è necessario modificare il pool di buffer, o forse considerare se ci sono anche indici indesiderati o duplicati che consumano respingente. Lo SHOW ENGINE INNODB STATUS \G offre anche un aspetto più dettagliato delle informazioni sul pool di buffer, incluso il suo pool di buffer individuale basato sul numero di innodb_buffer_pool_instances che hai impostato.
Utilizza gli indici FULLTEXT (ma solo se applicabile)
Utilizzo di query come,
SELECT bookid, page, context FROM books WHERE context like '%for dummies%';
in cui il contesto è una colonna di tipo stringa (char, varchar, testo), è un esempio di query pessima! L'estrazione di grandi contenuti di record con un filtro che deve essere avido finisce con una scansione completa della tabella, ed è semplicemente pazzesco. Prendi in considerazione l'utilizzo dell'indice FULLTEXT. Gli indici FULLTEXT hanno un design dell'indice invertito. Gli indici invertiti memorizzano un elenco di parole e, per ogni parola, un elenco di documenti in cui appare la parola. Per supportare la ricerca di prossimità, vengono memorizzate anche le informazioni sulla posizione per ogni parola, come offset di byte.
Per usare FULLTEXT per cercare o filtrare i dati, devi usare la combinazione di MATCH() ...AGAINST sintassi e non come la query sopra. Naturalmente, è necessario specificare il campo come campo dell'indice FULLTEXT.
Per creare un indice FULLTEXT, basta specificare con FULLTEXT come indice. Vedi l'esempio seguente:
root[minime]#> CREATE FULLTEXT INDEX aboutme_fts ON users_info(aboutme);
Query OK, 0 rows affected, 1 warning (0.49 sec)
Records: 0 Duplicates: 0 Warnings: 1
root[jbmrcd_date]#> show warnings;
+---------+------+--------------------------------------------------+
| Level | Code | Message |
+---------+------+--------------------------------------------------+
| Warning | 124 | InnoDB rebuilding table to add column FTS_DOC_ID |
+---------+------+--------------------------------------------------+
1 row in set (0.00 sec)
Sebbene l'utilizzo degli indici FULLTEXT possa offrire vantaggi durante la ricerca di parole all'interno di un contesto molto ampio all'interno di una colonna, crea anche problemi se utilizzati in modo errato.
Quando si esegue una ricerca FULLTEXT per una tabella di grandi dimensioni a cui si accede costantemente (in cui un certo numero di richieste del client cercano parole chiave diverse e univoche), potrebbe essere molto impegnativo per la CPU.
Ci sono anche alcune occasioni in cui FULLTEXT non è applicabile. Vedi questo post del blog esterno. Sebbene non l'abbia provato con 8.0, non vedo alcuna modifica rilevante a questo. Si consiglia di non utilizzare FULLTEXT per la ricerca in un ambiente di big data, in particolare per le tabelle ad alto traffico. Altrimenti, prova a sfruttare altre tecnologie come Apache Lucene, Apache Solr, tsearch2 o Sphinx.
Evita di usare NULL nelle colonne
Le colonne che contengono valori null vanno benissimo in MySQL. Ma se si utilizzano colonne con valori Null in un indice, ciò può influire sulle prestazioni della query poiché l'ottimizzatore non è in grado di fornire il piano di query corretto a causa della scarsa distribuzione dell'indice. Tuttavia, ci sono alcuni modi per ottimizzare le query che coinvolgono valori null ma, ovviamente, se ciò soddisfa i requisiti. Si prega di controllare la documentazione di MySQL sull'ottimizzazione nulla. Puoi anche controllare questo post esterno che è anche utile.
Progetta la topologia dello schema e la struttura delle tabelle in modo efficiente
In una certa misura, la normalizzazione delle tabelle del database da 1NF (First Normal Form) a 3NF (Third Normal Form) offre alcuni vantaggi per l'efficienza delle query poiché le tabelle normalizzate tendono a evitare record ridondanti. Una corretta pianificazione e progettazione per le tue tabelle è molto importante perché questo è il modo in cui hai recuperato o estratto i dati e in ognuna di queste azioni ha un costo. Con le tabelle normalizzate, l'obiettivo del database è garantire che ogni colonna non chiave in ogni tabella dipenda direttamente dalla chiave; l'intera chiave e nient'altro che la chiave. Se questo obiettivo viene raggiunto, ne ripaga i benefici sotto forma di licenziamenti ridotti, meno anomalie e maggiore efficienza.
Sebbene la normalizzazione delle tabelle abbia molti vantaggi, non significa che sia necessario normalizzare tutte le tabelle in questo modo. Puoi implementare un progetto per il tuo database usando Star Schema. La progettazione delle tabelle utilizzando lo schema a stella offre il vantaggio di query più semplici (evitare incroci incrociati complessi), facile recuperare i dati per la creazione di report, offre miglioramenti delle prestazioni perché non è necessario utilizzare unioni o join complessi o aggregazioni veloci. Uno Star Schema è semplice da implementare, ma è necessario pianificare con attenzione perché può creare grossi problemi e svantaggi quando il tuo tavolo diventa più grande e richiede manutenzione. Star Schema (e le sue tabelle sottostanti) sono soggetti a problemi di integrità dei dati, quindi potresti avere un'alta probabilità che una serie di dati sia ridondante. Se ritieni che questa tabella debba essere costante (struttura e design) ed è progettata per utilizzare l'efficienza delle query, allora è un caso ideale per questo approccio.
Miscelare i progetti del tuo database (purché tu sia in grado di determinare e identificare che tipo di dati devono essere estratti dalle tue tabelle) è molto importante poiché puoi trarre vantaggio da query più efficienti e oltre a aiutare il DBA con backup, manutenzione e ripristino.
Elimina i dati costanti e obsoleti
Di recente abbiamo scritto alcune best practice per l'archiviazione del database nel cloud. Descrive come sfruttare l'archiviazione dei dati prima che vada nel cloud. Quindi, in che modo eliminare i vecchi dati o archiviare i dati vecchi e costanti aiuta l'efficienza delle query? Come affermato nel mio precedente blog, ci sono vantaggi per le tabelle più grandi che vengono costantemente modificate e inserite con nuovi dati, lo spazio delle tabelle può crescere rapidamente. MySQL e InnoDB funzionano in modo efficiente quando record o dati sono contigui tra loro e hanno un significato per la riga successiva della tabella. Ciò significa che se non hai vecchi record che non devono più essere utilizzati, l'ottimizzatore non ha bisogno di includerli nelle statistiche offrendo risultati molto più efficienti. Ha senso, giusto? Inoltre, l'efficienza delle query non riguarda solo il lato dell'applicazione, ma deve anche considerare la sua efficienza durante l'esecuzione di un backup e durante la manutenzione o il failover. Ad esempio, se hai una query lunga e errata che può influire sul periodo di manutenzione o su un failover, questo può essere un problema.
Abilita la registrazione delle query secondo necessità
Imposta sempre il log delle query lente di MySQL in base alle tue esigenze personalizzate. Se stai utilizzando Percona Server, puoi sfruttare la loro registrazione lenta estesa delle query. Consente di definire in modo personalizzato determinate variabili. Puoi filtrare i tipi di query in combinazione come full_scan, full_join, tmp_table, ecc. Puoi anche dettare la velocità di registrazione lenta delle query tramite la variabile log_slow_rate_type e molte altre.
L'importanza di abilitare la registrazione delle query in MySQL (come le query lente) è utile per ispezionare le query in modo da poter ottimizzare o ottimizzare MySQL regolando determinate variabili che si adattano alle tue esigenze. Per abilitare il registro delle query lente, assicurati che queste variabili siano impostate:
- long_query_time:assegna il valore corretto per quanto tempo possono richiedere le query. Se le query impiegano più di 10 secondi (impostazione predefinita), il file di registro delle query lento che hai assegnato cadrà.
- slow_query_log - per abilitarlo, impostalo su 1.
- slow_query_log_file:questo è il percorso di destinazione per il file di registro delle query lente.
Il registro delle query lente è molto utile per l'analisi delle query e la diagnosi di query errate che causano stalli, ritardi degli slave, query a esecuzione prolungata, uso intensivo di memoria o CPU o persino il crash del server. Se utilizzi pt-query-digest o pt-index-usage, utilizza il file di registro delle query lente come destinazione di origine per segnalare allo stesso modo queste query.
Conclusione
Abbiamo discusso alcuni modi che puoi utilizzare per massimizzare l'efficienza delle query del database in questo blog. Nella prossima parte discuteremo ancora di più fattori che possono aiutarti a massimizzare le prestazioni. Resta sintonizzato!