Mysql
 sql >> Database >  >> RDS >> Mysql

MySQL:sempre in attesa del blocco dei metadati della tabella

La soluzione accettata è, sfortunatamente, errata . È giusto per quanto si dice,

Questo è infatti (quasi certamente; vedi sotto) cosa fare. Ma poi suggerisce,

...e 1398 non lo è il collegamento con la serratura. Come potrebbe essere? 1398 è la connessione in attesa per la serratura. Ciò significa che non ha ancora la serratura, e quindi, ucciderla non serve a nulla. Il processo che mantiene il blocco manterrà ancora il blocco e il successivo il thread che tenta di fare qualcosa sarà quindi anche stallo e inserisci "In attesa di blocco dei metadati" nell'ordine dovuto.

Non hai alcuna garanzia che anche i processi "in attesa di blocco dei metadati" (WFML) non si bloccheranno, ma puoi essere certo che uccidere solo i processi WFML non porterà esattamente a nulla .

La vera causa è che un altro processo sta bloccando e, soprattutto, SHOW FULL PROCESSLIST non ti dirà direttamente quale sia .

SARA' dirti se il processo è in corso qualcosa, sì. Di solito funziona. Qui, il processo che tiene il lucchetto non sta facendo nulla , e si nasconde tra gli altri thread anche senza fare nulla.

In questo caso il colpevole è quasi certamente processo 1396 , iniziato prima del processo 1398 e ora in Sleep stato, ed è stato per 46 secondi. Dal 1396 ha fatto chiaramente tutto ciò che doveva fare (come dimostrato dal fatto che ora sta dormendo, e lo ha fatto per 46 secondi, per quanto riguarda MySQL ), nessun thread è andato a dormire prima che avrebbe potuto contenere un lucchetto (o anche 1396 si sarebbe bloccato).

IMPORTANTE :se ti sei connesso a MySQL come utente limitato, SHOW FULL PROCESSLIST non mostra tutti i processi Quindi il blocco potrebbe essere trattenuto da un processo che non vedi.

Un migliore SHOW PROCESSLIST

SELECT ID, TIME, USER, HOST, DB, COMMAND, STATE, INFO
    FROM INFORMATION_SCHEMA.PROCESSLIST WHERE DB IS NOT NULL
    AND (`INFO` NOT LIKE '%INFORMATION_SCHEMA%' OR INFO IS NULL)
    ORDER BY `DB`, `TIME` DESC

Quanto sopra può essere regolato per mostrare solo i processi nello stato SLEEP, e comunque li ordinerà per tempo decrescente, quindi è più facile trovare il processo che è sospeso (di solito è il Sleep 'ing uno immediatamente prima di quelli "in attesa di blocco dei metadati".

La cosa importante

Lascia il processo di "attesa per il blocco dei metadati" solo .

Soluzione rapida e sporca, non proprio consigliata ma veloce

Uccidi tutti processi in stato "Sleep", sullo stesso database, che sono più vecchi del più vecchio thread nello stato "in attesa di blocco dei metadati". Ecco cosa Arnaud Amaury avrebbe fatto:

  • per ogni database che ha almeno un thread in WaitingForMetadataLock:
    • la connessione più vecchia in WFML su quel DB risulta essere vecchia di Z secondi
    • TUTTI i thread "Sleep" su quel DB e precedenti a Z devono essere eliminati. Inizia con quelli più freschi, per ogni evenienza.
    • Se esiste una connessione più vecchia e non dormiente su quel DB, allora forse è quella che tiene il blocco, ma sta facendo qualcosa . Ovviamente puoi ucciderlo, ma soprattutto se si tratta di un UPDATE/INSERT/DELETE, lo fai a tuo rischio e pericolo.

Novantanove volte su cento, il filo da uccidere è il più giovane tra quelli in stato di Sonno che sono più vecchi rispetto a quello precedente in attesa del blocco dei metadati:

TIME     STATUS
319      Sleep
205      Sleep
 19      Sleep                      <--- one of these two "19"
 19      Sleep                      <--- and probably this one(*)
 15      Waiting for metadata lock  <--- oldest WFML
 15      Waiting for metadata lock
 14      Waiting for metadata lock

(*) l'ordine TIME ha effettivamente millisecondi, o almeno così mi è stato detto, semplicemente non li mostra. Quindi, mentre entrambi i processi hanno un valore Time di 19, quello più basso dovrebbe essere più giovane.

Risoluzione più mirata

Esegui SHOW ENGINE INNODB STATUS e guarda la sezione "TRANSAZIONE". Troverai, tra gli altri, qualcosa come

TRANSACTION 1701, ACTIVE 58 sec;2 lock struct(s), heap size 376, 1 row lock(s), undo log entries 1
MySQL thread id 1396, OS thread handle 0x7fd06d675700, query id 1138 hostname 1.2.3.4 whatever;

Ora controlla con SHOW FULL PROCESSLIST cosa sta facendo l'ID thread 1396 con la sua transazione #1701. È probabile che sia nello stato "Sleep". Quindi:una transazione attiva (#1701) con un blocco attivo, ha anche apportato alcune modifiche poiché ha una voce di registro annullamenti... ma è attualmente inattiva. Quello e nessun altro è il thread che devi uccidere. Perdere quei cambiamenti.

Ricorda che non fare nulla in MySQL non significa fare nulla in generale. Se ottieni alcuni record da MySQL e crei un CSV per il caricamento FTP, durante il caricamento FTP la connessione MySQL è inattiva.

In realtà, se il processo che utilizza MySQL e il server MySQL sono sulla stessa macchina, quella macchina esegue Linux e tu hai i privilegi di root, c'è un modo per scoprire quale processo ha la connessione che ha richiesto il blocco. Questo a sua volta consente di determinare (dall'utilizzo della CPU o, nel peggiore dei casi, strace -ff -p pid ) se quel processo è realmente fare qualcosa o meno, per aiutare a decidere se è sicuro uccidere.

Perché succede?

Vedo che ciò accade con le webapp che utilizzano connessioni MySQL "persistenti" o "in pool", che al giorno d'oggi di solito fanno risparmiare pochissimo tempo:l'istanza webapp è terminata, ma la connessione no , quindi il suo blocco è ancora vivo... e blocca tutti gli altri.

Un altro modo interessante che ho trovato è, nelle ipotesi precedenti, eseguire una query restituendo alcune righe, e recuperarne solo alcune . Se la query non è impostata su "pulizia automatica" (tuttavia il DBA sottostante lo fa), manterrà la connessione aperta e impedirà il blocco completo della tabella. Mi è successo questo in un pezzo di codice che verificava se esisteva una riga selezionando quella riga e verificando se ha ricevuto un errore (non esiste) o meno (deve esistere), ma senza effettivamente recuperare la riga .

Chiedi al DB

Un altro modo per ottenere il colpevole se hai un MySQL recente, ma non troppo recente poiché questo sarà ritirato , è (sono necessari di nuovo i privilegi sullo schema delle informazioni)

SELECT * FROM INFORMATION_SCHEMA.INNODB_LOCKS 
     WHERE LOCK_TRX_ID IN 
        (SELECT BLOCKING_TRX_ID FROM INFORMATION_SCHEMA.INNODB_LOCK_WAITS);

Soluzione reale, che richiede tempo e lavoro

Il problema è solitamente causato da questa architettura:

Quando la webapp si interrompe o l'istanza del thread leggero webapp si interrompe, il contenitore/pool di connessione potrebbe non funzionare . Ed è il contenitore che mantiene la connessione aperta, quindi ovviamente la connessione non si chiude. Abbastanza prevedibilmente, MySQL non considera l'operazione completata .

Se l'app web non è stata pulita dopo se stessa (nessun ROLLBACK o COMMIT per una transazione, nessun UNLOCK TABLES , ecc.), quindi qualsiasi cosa quella webapp abbia iniziato a fare è ancora esistente , e potrebbe continuare a bloccare tutti gli altri.

Ci sono quindi due soluzioni. Il peggio è ridurre il timeout di inattività . Ma indovina cosa succede se aspetti troppo a lungo tra due query (esattamente:"Il server MySQL è andato via"). Puoi quindi utilizzare mysql_ping se disponibile (presto ritirato. Ci sono soluzioni alternative per DOP. Oppure potresti verificare quello errore e riaprire la connessione se accade (questo è il modo Python). Quindi, con una piccola commissione di performance, è fattibile.

La soluzione migliore e più intelligente è meno semplice da implementare. Cerca di fare in modo che lo script pulisca da solo, assicurandoti di recuperare tutte le righe o di liberare tutte le risorse di query, catturare tutte le eccezioni e gestirle correttamente o, se possibile, ignorare del tutto le connessioni persistenti . Consenti a ciascuna istanza di creare la propria connessione o utilizza un pilota della piscina (in PHP PDO, usa PDO::ATTR_PERSISTENT impostato esplicitamente su false ). In alternativa (es.

Non conosco un modo per interrogare le risorse del set di risultati esistenti per liberarle; l'unico modo sarebbe salvare quelle risorse in un array privato.