sql >> Databasteknik >  >> RDS >> Mysql

MySQL:Får permanent väntar på tabellmetadatalås

Den accepterade lösningen är tyvärr fel . Det är rätt så långt det står,

Detta är verkligen (nästan säkert; se nedan) vad du ska göra. Men då tyder det på,

...och 1398 är inte kopplingen med låset. Hur kommer det sig? 1398 är anslutningen väntar för låset. Det betyder att den inte har ännu låset, och därför hjälper det ingenting att döda det. Processen som håller låset kommer fortfarande att hålla låset, och nästa tråd som försöker göra något kommer därför också stanna och ange "Väntar på metadatalås" i rätt ordning.

Du har ingen garanti för att processerna som "väntar på metadatalås" (WFML) inte också blockerar, men du kan vara säker på att om du bara dödar WFML-processer kommer du att uppnå exakt ingenting .

Den verkliga orsaken är att en annan process håller låset , och ännu viktigare, SHOW FULL PROCESSLIST kommer inte att berätta direkt vad det är .

Det KOMMER tala om för dig om processen pågår något, ja. Vanligtvis fungerar det. Här gör processen som håller låset ingenting , och gömmer sig bland andra trådar också gör ingenting.

I det här fallet är den skyldige nästan säkert process 1396 , som startade före process 1398 och nu är i Sleep tillstånd och har varit i 46 sekunder. Sedan 1396 gjorde helt klart allt som den behövde göra (vilket bevisas av det faktum att den nu sover, och har gjort det i 46 sekunder, när det gäller MySQL ), ingen tråd som hade gått i vila innan som kunde ha hållit ett lås (eller så skulle 1396 också ha stannat).

VIKTIGT :om du anslutit till MySQL som en begränsad användare, SHOW FULL PROCESSLIST kommer inte visa alla processer. Så låset kan hållas av en process som du inte ser.

En bättre 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

Ovanstående kan ställas in för att bara visa processerna i SLEEP-tillståndet, och hur som helst kommer det att sortera dem efter fallande tid, så det är lättare att hitta processen som hänger (det är vanligtvis Sleep 'ing en omedelbart före de "väntar på metadatalås").

Det viktiga

Lämna alla processer för "väntar på metadatalås" ensamma .

Snabb och smutsig lösning, rekommenderas inte riktigt men snabb

Döda alla processer i "viloläge", på samma databas, som är äldre än de äldsta tråden i tillståndet "väntar på metadatalås". Detta är vad Arnaud Amaury skulle ha gjort:

  • för varje databas som har minst en tråd i WaitingForMetadataLock:
    • den äldsta anslutningen i WFML på den DB visar sig vara Z sekunder gammal
    • ALLA "Sömn"-trådar på den DB och äldre än Z måste försvinna. Börja med de färskaste, för säkerhets skull.
    • Om det finns en äldre och icke-sovande anslutning på den DB, så kanske det är den som håller låset, men den gör något . Du kan naturligtvis döda det, men speciellt om det är en UPPDATERING/INSERT/DELETE gör du det på egen risk.

Nittionio gånger av hundra är tråden som ska dödas den yngsta bland dem i sömntillstånd som är äldre än den äldre som väntar på metadatalås:

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

(*) TIME-ordern har faktiskt millisekunder, eller så fick jag veta, den visar dem bara inte. Så medan båda processerna har ett tidsvärde på 19, borde den lägsta vara yngre.

Mer fokuserad fix

Kör SHOW ENGINE INNODB STATUS och titta på avsnittet "TRANSAKTION". Du hittar bland annat något liknande

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;

Nu kollar du med SHOW FULL PROCESSLIST vad gör tråd-id 1396 med sin #1701-transaktion. Chansen är stor att den är i "Sömn"-status. Så:en aktiv transaktion (#1701) med ett aktivt lås, den har till och med gjort några ändringar eftersom den har en ångra loggpost... men är för närvarande inaktiv. Det och ingen annan är tråden du behöver döda. Förlorar dessa ändringar.

Kom ihåg att att göra ingenting i MySQL betyder inte att göra ingenting i allmänhet. Om du hämtar några poster från MySQL och bygger en CSV för FTP-uppladdning, är MySQL-anslutningen inaktiv under FTP-uppladdningen.

Om processen som använder MySQL och MySQL-servern är på samma maskin, den maskinen kör Linux och du har root-privilegier, finns det ett sätt att ta reda på vilken process har anslutningen som begärde låset. Detta gör det i sin tur möjligt att bestämma (från CPU-användning eller, i värsta fall, strace -ff -p pid ) om den processen verkligen är göra något eller inte, för att hjälpa till att avgöra om det är säkert att döda.

Varför händer detta?

Jag ser detta hända med webbappar som använder "beständiga" eller "poolade" MySQL-anslutningar, vilket numera vanligtvis sparar väldigt lite tid:webbappsinstansen avslutades, men anslutningen gjorde det inte , så dess lås är fortfarande vid liv... och blockerar alla andra.

Ett annat intressant sätt som jag hittade är, i hypoteserna ovan, att köra en fråga som returnerar några rader, och bara hämta några av dem . Om frågan inte är inställd på "auto-clean" (hur den underliggande DBA än gör det), kommer den att hålla anslutningen öppen och förhindra att ett helt lås på bordet går igenom. Det här hände mig i en kod som verifierade om en rad existerade genom att välja den raden och verifiera om den fick ett fel (finns inte) eller inte (det måste finnas), men utan att faktiskt hämta raden .

Fråga DB

Ett annat sätt att få tag i den skyldige om du har en ny MySQL, men inte för ny eftersom det här kommer att fasas ut , är (du behöver privilegier igen på informationsschemat)

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

Faktisk lösning som kräver tid och arbete

Problemet orsakas vanligtvis av denna arkitektur:

När webbappen dör, eller webbappens lättviktstrådsinstans dör, kan det hända att behållaren/anslutningspoolen inte . Och det är behållaren som håller anslutningen öppen, så uppenbarligen stängs inte anslutningen. Ganska förutsägbart anser MySQL inte att operationen är avslutad .

Om webbappen inte rensade efter sig själv (ingen ROLLBACK eller COMMIT för en transaktion, ingen UNLOCK TABLES , etc.), sedan finns allt som webbappen började göra fortfarande , och kanske fortfarande blockerar alla andra.

Det finns då två lösningar. Det värsta är att sänka tidsgränsen för vilotid . Men gissa vad som händer om du väntar för länge mellan två frågor (exakt:"MySQL-servern har gått bort"). Du kan sedan använda mysql_ping om tillgängligt (kommer snart att fasas ut. Det finns lösningar för SUB. Eller du kanske kontrollerar det fel och öppna anslutningen igen om det händer (detta är Python-sättet). Så - för en liten prestationsavgift - är det genomförbart.

Den bättre, smartare lösningen är mindre enkel att implementera. Sträva efter att få skriptet rent efter sig, se till att hämta alla rader eller frigöra alla frågeresurser, fånga alla undantag och hantera dem på rätt sätt, eller, om möjligt, hoppa över beständiga anslutningar helt och hållet . Låt varje instans skapa sin egen anslutning eller använd en smart poolförare (i PHP PDO, använd PDO::ATTR_PERSISTENT uttryckligen inställd på false ). Alternativt (t.ex. i PHP) kan du låta destruct- och undantagshanterare tvinga rensning av anslutningen genom att utföra eller återställa transaktioner och utfärda explicita tabellupplåsningar.

Jag känner inte till något sätt att söka efter befintliga resultatresurser för att frigöra dem; det enda sättet skulle vara att spara dessa resurser i en privat array.



  1. Hibernate, MySQL och tabell med namnet Repeat - konstigt beteende

  2. Jämför datum lagrade som sträng med Datetime

  3. Hur man sammanfogar text från flera rader till en enda textsträng i SQL Server

  4. Villkorlig lead/lag-funktion PostgreSQL?