Ragioni per domande occasionalmente lente?


16

Stiamo eseguendo MySQL 5.1 su Windows Server 2008 R2.

Ultimamente abbiamo effettuato alcune diagnosi sul nostro database e abbiamo trovato alcuni artefatti fastidiosi che non possiamo spiegare . Abbiamo aggiunto un po 'di codice per registrare quando abbiamo avuto query che hanno richiesto molto tempo (> 2000 ms). I risultati sono stati sorprendenti (e forse una spiegazione per i nostri deadlock).

Occasionalmente le query, che normalmente richiedono pochissimo tempo (<10 ms), impiegano da 4 a 13 secondi. Per essere chiari, si tratta di query che sono in esecuzione costantemente (più volte al secondo) e non soffrono di questi picchi di tempo delle query.

Abbiamo esaminato i nostri indici alla ricerca di errori evidenti e non abbiamo avuto molta fortuna.

Aggiornare

La tabella delle persone:

| people | CREATE TABLE `people` (
`people_id` bigint(20) NOT NULL AUTO_INCREMENT,
`company_id` bigint(20) NOT NULL,
`name` varchar(255) DEFAULT NULL,
`password` varchar(255) DEFAULT NULL,
`temp_password` varchar(10) DEFAULT NULL,
`reset_password_hash` varchar(255) DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`phone` varchar(32) DEFAULT NULL,
`mobile` varchar(32) DEFAULT NULL,
`iphone_device_id` varchar(160) DEFAULT NULL,
`iphone_device_time` datetime DEFAULT NULL,
`last_checkin` datetime DEFAULT NULL,
`location_lat` double DEFAULT NULL,
`location_long` double DEFAULT NULL,
`gps_strength` smallint(6) DEFAULT NULL,
`picture_blob_id` bigint(20) DEFAULT NULL,
`authority` int(11) NOT NULL DEFAULT '0',
`active` tinyint(1) NOT NULL DEFAULT '1',
`date_created` datetime NOT NULL,
`last_login` datetime NOT NULL,
`panic_mode` tinyint(1) NOT NULL DEFAULT '0',
`battery_level` double DEFAULT NULL,
`battery_state` varchar(32) DEFAULT NULL,
PRIMARY KEY (`people_id`),
KEY `email` (`email`),
KEY `company_id` (`company_id`),
KEY `iphone_device_id` (`iphone_device_id`),
KEY `picture_blob_id` (`picture_blob_id`),
CONSTRAINT `people_ibfk_1` FOREIGN KEY (`company_id`) REFERENCES `companies` (`company_id`) ON DELETE CASCADE ON UPDATE CASCADE,
CONSTRAINT `people_ibfk_2` FOREIGN KEY (`picture_blob_id`) REFERENCES `blobs` (`blob_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=4658 DEFAULT CHARSET=utf8 |

indici:

+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| Table  | Non_unique | Key_name         | Seq_in_index | Column_name      | Collation | Cardinality | Sub_part | Packed | Null | Index_type | Comment |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+
| people |          0 | PRIMARY          |            1 | people_id        | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | email            |            1 | email            | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | company_id       |            1 | company_id       | A         |        3502 |     NULL | NULL   |      | BTREE      |         |
| people |          1 | iphone_device_id |            1 | iphone_device_id | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
| people |          1 | picture_blob_id  |            1 | picture_blob_id  | A         |        3502 |     NULL | NULL   | YES  | BTREE      |         |
+--------+------------+------------------+--------------+------------------+-----------+-------------+----------+--------+------+------------+---------+

abbiamo ~ 5000 righe nella tabella sul server che ci sta dando problemi.


1
C'è qualcosa che non hai ancora mostrato nelle precedenti due domande. Per favore aggiungi a questa domanda tre (3) cose: 1) MOSTRA CREA TABELLA persone \ G 2) MOSTRA INDICI DA persone; 3) SELEZIONA COUNT (1) DA persone;
RolandoMySQLDBA

@RolandoMySQLDBA Lo farò non appena mi metterò al lavoro domani. Saluti :)
RedBlueThing

Ho aggiornato la mia risposta. Si prega di leggere !!!
RolandoMySQLDBA,

@RolandoMySQLDBA Grazie :). Analizzando ancora questa roba. Ti farò sapere come andiamo.
RedBlue:

Risposte:


14

Le query di AGGIORNAMENTO delle precedenti due domande ( Domanda 1 , Domanda 2 ) stanno colpendo la tabella "persone" con PRIMARY KEY con blocco a livello di riga. Questo è ciò che ho affermato nella domanda 1 del 6 giugno 2011 alle 10:03

Tutte le transazioni attraversano la chiave PRIMARY. Poiché PRIMARY è un indice cluster in InnoDB, la chiave PRIMARY e la riga stessa sono insieme. Quindi, attraversare una riga e il PRIMARY KEY sono la stessa cosa. Pertanto, qualsiasi blocco dell'indice sul PRIMARY KEY è anche un blocco a livello di riga.

Qualcos'altro non è stato ancora considerato che può attribuire lentezza agli indici: l'uso di indici NON UNICI in InnoDB. Ogni ricerca indicizzata in InnoDB che utilizza indici non univoci ha anche l'ID riga di ogni riga collegata alla chiave non univoca. Il rowID emana sostanzialmente dall'indice cluster . L'aggiornamento di indici non univoci DEVE SEMPRE interagire con l'indice cluster ANCHE SE LA TABELLA NON HA UN TASTO PRIMARIO.

Un'altra cosa a cui pensare è il processo di gestione dei nodi BTREE in un indice. A volte, richiede la divisione della pagina dei nodi. Tutte le voci nel nodo BTREE di indici non univoci contengono campi non univoci PIÙ il rowID all'interno dell'indice cluster. Per mitigare correttamente la divisione di tali pagine BTREE senza disturbare l'integrità dei dati, la riga associata all'ID riga deve presentare un blocco a livello di riga internamente.

Se la tabella "persone" ha molti indici non univoci, preparati ad avere un gran numero di pagine indice nel tablespace, oltre ad avere piccoli blocchi di piccole file che ti si intrufolano di tanto in tanto.

C'è un altro fattore che non è così ovvio: la popolazione chiave

A volte, quando un indice viene popolato, i valori chiave che compongono gli indici potrebbero diventare sbilenco nel tempo e causare il passaggio da MySQL Query Optimizer a ricerche con chiave, a scansioni di indici e infine a scansioni di tabelle complete. Che non è possibile controllare se non si riprogetta la tabella con nuovi indici per compensare le chiavi di sbilenco ot.Fornisci la struttura della tabella per la tabella "persone", il conteggio della tabella "persone" e l'output degli indici di visualizzazione per la tabella "persone" .

Anche se le query utilizzano solo il PRIMARY KEY, la sbilenco delle chiavi negli indici non univoci richiede comunque il bilanciamento BTREE e la suddivisione della pagina. Tale gestione BTREE produrrà un notevole rallentamento a causa di blocchi intermittenti a livello di riga che non si prevede di accadere.

AGGIORNAMENTO 2011-06-14 22:19

Domande dalla domanda 1

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>',
iphone_device_time = '2011-06-06 05:35:09', last_checkin = '2011-06-06 05:24:42',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

UPDATE people SET company_id = 1610, name = '<name>', password = '<hash>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@yahoo.com',
phone = NULL, mobile = '<phone>', iphone_device_id = 'android:<id>-<id>-<id>-<id>',
iphone_device_time = '2011-06-06 05:24:42', last_checkin = '2011-06-06 05:35:07',
location_lat = <lat>, location_long = -<lng>, gps_strength = 3296,
picture_blob_id = 1190,
authority = 1, active = 1, date_created = '2011-04-13 20:21:20',
last_login = '2011-06-06 05:35:09', panic_mode = 0,
battery_level = NULL, battery_state = NULL WHERE people_id = 3125

Immagina la sequenza negli eventi

  1. Trova la riga tramite PRIMARY KEY
  2. Blocca la riga e l'indice cluster
  3. Crea dati MVCC per tutte le colonne da aggiornare
  4. Quattro colonne sono indicizzate (email, company_id, iphone_device_id, picture_blob_id)
  5. Ogni indice richiede la gestione BTREE
  6. All'interno dello stesso spazio di transazione, i passaggi 1-5 stanno tentando di essere ripetuti sulla stessa riga, aggiornando le stesse colonne (invia lo stesso messaggio di posta elettronica in entrambe le query, company_id è lo stesso in entrambe le query, picture_blob_id è lo stesso in entrambe le query, iphone_device_id diverso)

Domande dalla domanda 2

UPDATE people SET iphone_device_id=NULL
WHERE iphone_device_id='iphone:<device_id_blah>' AND people_id<>666;

UPDATE people SET company_id = 444, name = 'Dad', password = '<pass>',
temp_password = NULL, reset_password_hash = NULL, email = '<redacted>@gmail.com',
phone = NULL, mobile = NULL, iphone_device_id = 'iphone:<device_id_blah>',
iphone_device_time = '2011-06-06 19:12:29', last_checkin = '2011-06-07 02:49:47',
location_lat = <lat>, location_long = <lng>, gps_strength = 66,
picture_blob_id = 1661,
authority = 1, active = 1, date_created = '2011-03-20 19:18:34',
last_login = '2011-06-07 11:15:01', panic_mode = 0, battery_level = 0.55,
battery_state = 'unplugged' WHERE people_id = 666;

Queste due query sono ancora più confuse perché la prima query aggiorna tutto tranne people_id 666. Centinaia di righe sono dolorosamente bloccate solo con la prima query. La seconda query sta aggiornando people_id 666 eseguendo la sequenza di 5 eventi. La prima query esegue quelle stesse 5 sequenze di eventi su ogni riga coinvolta tranne people_id 666 ma l'indice per iphone_device_id si trova su un percorso interecept con due query diverse. Qualcuno deve bloccare le pagine BTREE in base all'ordine di arrivo.

Di fronte a queste due coppie di query su un percorso di collisione, eventualmente bloccare le stesse pagine BTREE all'interno di un indice può essere un'esperienza sconvolgente per InnoDB o qualsiasi RDBMS conforme ACID. Pertanto, un rallentamento dell'indice è il destino di queste coppie di query a meno che non sia possibile garantire che le query vengano eseguite con AUTOCOMMIT = 1 o consentendo letture sporche (anche se collisioni come queste rendono READ-COMMITTED e READ-UNCOMMITED un incubo per MVCC).

AGGIORNAMENTO 2011-06-15 10:29

@RedBlueThing: nelle query della domanda 2, la prima query è una query a intervallo, quindi vengono raggiunti molti blocchi di riga. Si noti inoltre che entrambe le query stanno tentando di bloccare lo stesso spazio ID pagina 0 n. 4611 n bit 152 è bloccato in PRIMARY KEY, noto anche come indice cluster.

Per assicurarti che l'app sia almeno in esecuzione in base alla serie di eventi previsti, ci sono due diverse opzioni che potresti provare:

Opzione 1) Convertire questa tabella in MyISAM (almeno su un server di sviluppo). Ogni UPDATE, INSERT e DELETE imporranno un blocco completo della tabella in base all'ordine di arrivo.

Opzione 2) Provare a utilizzare il livello di isolamento SERIALIZZABILE . Ciò bloccherà tutte le righe previste in modalità CONDIVISO.

La sequenza di eventi previsti si interromperà o avrà esito positivo utilizzando queste due opzioni alternative. Se entrambe queste opzioni falliscono, dovrai guardare l'app e dare priorità all'ordine di esecuzione delle tue query. Una volta stabilita quella priorità, puoi semplicemente annullare queste opzioni (per l'opzione 1, torna a InnoDB, per l'opzione 2, torna al livello di isolamento predefinito [smetti di usare SERIALIZABLE]).


@RolandoMySQLDBA Ho aggiornato la nostra domanda con i dettagli richiesti.
RedBlueThing

@RolandoMySQLDBA Grazie per dare un'altra occhiata a questo. Mi chiedevo, commenti per la domanda 2, perché la prima query dovrebbe bloccare centinaia di righe? Non bloccherebbe solo le righe non 666 che corrispondono all'ID del dispositivo? (cioè una singola riga)
RedBlueThing

@RolandoMySQLDBA Sulla base del tuo suggerimento della domanda 1, abbiamo verificato la nostra impostazione di autocommit e confermato che è attivata.
RedBlueThing

@RolandoMySQLDBA Esiste un problema specifico con le query della prima domanda (oltre all'aggiornamento di tutti i campi della riga). Qualcosa che spiegherebbe un tempo di esecuzione di 13 secondi per la query? Ho l'impressione che indicizzare quattro colonne non sia qualcosa che consiglieresti, ma ciò comporterebbe davvero prestazioni così scarse?
RedBlueThing

@RolandoMySQLDBA +1 e grazie per tutti i tuoi suggerimenti. Non abbiamo finito per modificare il livello di isolamento per risolvere il problema. Invece abbiamo effettuato aggiornamenti parziali sul campo per la domanda 2 e ottimizzato una query nel percorso di aggiornamento. Ecco! non più deadlock. :)
RedBlueThing

3

MOSTRA VARIABILI COME 'innodb%'; - In particolare, se i dati e gli indici non hanno appena raggiunto la dimensione del pool di buffer, è possibile che il disco venga colpito molto più duramente di prima. L'I / O è il killer ad alte prestazioni.

La maggior parte dei campi è due volte più grande del necessario. BIGINT (8 byte) è eccessivo per la maggior parte degli ID. 5000 righe hanno bisogno solo di UN PICCOLO SOTTOMINATO (limite di 65 KB, solo 2 byte). Oppure usa MEDIUMINT per un margine di sicurezza.

DOUBLE ti dà 16 cifre significative al costo di 8 byte. Battery_level ha più di 2 cifre significative di precisione? FLOAT richiede 4 byte.

Il mio punto qui è che "più piccolo -> più memorizzabile nella cache -> più veloce".

Per favore, mostraci le domande lente; almeno alcuni di quelli che sono improvvisamente diventati più lenti. Possiamo solo fare ipotesi senza di loro. Attiva lo slowlog e imposta long_query_time = 1; questi aiuteranno a trovare le query più lente.

Comprendi i vantaggi degli indici "composti"?

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.