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

MySQL:Får permanent Venter på tabelmetadatalås

Den accepterede løsning er desværre forkert . Det er rigtigt, så vidt der står,

Dette er faktisk (næsten sikkert; se nedenfor), hvad du skal gøre. Men så tyder det på,

...og 1398 er ikke forbindelsen med låsen. Hvordan kunne det være? 1398 er forbindelsen venter for låsen. Det betyder, at den endnu ikke har låsen, og derfor hjælper det intet at slå den ihjel. Processen, der holder låsen, vil stadig holde låsen, og den næste tråd, der forsøger at gøre noget, vil derfor også gå i stå, og indtast "Venter på metadatalås" i den rigtige rækkefølge.

Du har ingen garanti for, at processerne "waiting for metadata lock" (WFML) ikke også blokerer, men du kan være sikker på, at det at dræbe kun WFML-processer vil opnå præcis intet .

Den virkelige årsag er, at en anden proces holder låsen , og endnu vigtigere, SHOW FULL PROCESSLIST vil ikke fortælle dig direkte, hvad det er .

Det VIL fortælle dig, om processen i gang noget, ja. Normalt virker det. Her gør processen, der holder låsen, ingenting , og skjuler blandt andre tråde også gør ingenting.

I dette tilfælde er synderen næsten helt sikkert proces 1396 , som startede før proces 1398 og nu er i Sleep tilstand, og har været det i 46 sekunder. Siden 1396 har den klart gjort alt, hvad den behøvede at gøre (som bevist af det faktum, at den nu sover, og har gjort det i 46 sekunder, hvad angår MySQL ), ingen tråd, der var gået i dvale før, kunne have holdt en lås (eller 1396 ville også være gået i stå).

VIGTIG :hvis du har oprettet forbindelse til MySQL som en begrænset bruger, SHOW FULL PROCESSLIST vil ikke vise alle processerne. Så låsen kan blive holdt af en proces, som du ikke kan se.

En bedre 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

Ovenstående kan indstilles til kun at vise processerne i SLEEP-tilstand, og alligevel vil den sortere dem efter faldende tid, så det er nemmere at finde den proces, der hænger (det er normalt Sleep 'ing en umiddelbart før dem, der "venter på metadatalås").

Det vigtige

Lad enhver "venter på metadatalås"-proces alene .

Hurtig og beskidt løsning, ikke rigtig anbefalet, men hurtig

Dræb alle processer i "Søvn"-tilstand på den samme database, som er ældre end den ældste tråd i tilstanden "venter på metadatalås". Dette er hvad Arnaud Amaury ville have gjort:

  • for hver database, der har mindst én tråd i WaitingForMetadataLock:
    • den ældste forbindelse i WFML på den DB viser sig at være Z sekunder gammel
    • ALLE "Søvn"-trådene på den DB og ældre end Z skal forsvinde. Start med de friskeste, for en sikkerheds skyld.
    • Hvis der findes en ældre og ikke-sovende forbindelse på den DB, så er det måske den, der holder låsen, men den gør noget . Du kan selvfølgelig slå det ihjel, men især hvis det er en OPDATERING/INDSÆT/SLET, gør du det på egen risiko.

Nioghalvfems gange ud af hundrede er tråden, der skal dræbes, den yngste blandt dem i søvntilstand, der er ældre end den ældre, der venter 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-ordren har faktisk millisekunder, eller det fik jeg at vide, den viser dem bare ikke. Så mens begge processer har en tidsværdi på 19, burde den laveste være yngre.

Mere fokuseret rettelse

Kør SHOW ENGINE INNODB STATUS og se på afsnittet "TRANSAKTION". Du finder blandt andet noget lignende

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 tjekker du med SHOW FULL PROCESSLIST hvad laver tråd-id 1396 med sin #1701-transaktion. Chancerne er, at den er i "Søvn"-status. Så:en aktiv transaktion (#1701) med en aktiv lås, den har endda foretaget nogle ændringer, da den har en fortryd-logpost... men er i øjeblikket inaktiv. Det og ingen anden er den tråd, du skal slå ihjel. At miste disse ændringer.

Husk, at det at gøre ingenting i MySQL ikke betyder, at du ikke gør noget generelt. Hvis du henter nogle poster fra MySQL og bygger en CSV til FTP-upload, er MySQL-forbindelsen inaktiv under FTP-uploaden.

Faktisk, hvis processen, der bruger MySQL og MySQL-serveren er på den samme maskine, den maskine kører Linux, og du har root-rettigheder, er der en måde at finde ud af, hvilken proces har den forbindelse, der anmodede om låsen. Dette gør det igen muligt at bestemme (ud fra CPU-brug eller i værste fald strace -ff -p pid ) om den proces virkelig er gør noget eller ej, for at hjælpe med at afgøre, om det er sikkert at dræbe.

Hvorfor sker det?

Jeg ser dette ske med webapps, der bruger "vedvarende" eller "poolede" MySQL-forbindelser, som i dag normalt sparer meget lidt tid:webapp-forekomsten blev afsluttet, men forbindelsen gjorde det ikke , så dens lås er stadig i live... og blokerer alle andre.

En anden interessant måde som jeg fandt er, i hypoteserne ovenfor, at køre en forespørgsel, der returnerer nogle rækker, og kun hente nogle af dem . Hvis forespørgslen ikke er sat til "auto-clean" (uanset den underliggende DBA gør det), vil det holde forbindelsen åben og forhindre en fuld lås på bordet i at gå igennem. Det skete for mig i et stykke kode, der bekræftede, om en række eksisterede, ved at vælge den række og kontrollere, om den fik en fejl (findes ikke) eller ej (den skal eksistere), men uden faktisk at hente rækken .

Spørg DB

En anden måde at få fat i synderen, hvis du har en nylig MySQL, men ikke for nylig da dette vil blive udfaset , er (du skal bruge privilegier igen på informationsskemaet)

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

Faktisk løsning, der kræver tid og arbejde

Problemet er normalt forårsaget af denne arkitektur:

Når webappen dør, eller webappens letvægtstrådsinstans dør, beholderen/forbindelsespuljen er muligvis ikke . Og det er beholderen der holder forbindelsen åben, så åbenbart lukker forbindelsen ikke. Helt forudsigeligt betragter MySQL ikke operationen som afsluttet .

Hvis webappen ikke rensede efter sig selv (ingen ROLLBACK eller COMMIT for en transaktion, ingen UNLOCK TABLES osv.), så er alt hvad den webapp begyndte at gøre stadig , og blokerer muligvis stadig alle andre.

Så er der to løsninger. Det værre er at sænke timeoutet for tomgang . Men gæt hvad der sker, hvis du venter for længe mellem to forespørgsler (præcis:"MySQL-serveren er gået væk"). Du kan derefter bruge mysql_ping hvis tilgængeligt (snart udfaset. Der er løsninger for BOB. Eller du kan tjekke for det fejl, og genåbn forbindelsen, hvis det sker (dette er Python-måden). Så - for et lille præstationsgebyr - kan det lade sig gøre.

Den bedre, smartere løsning er mindre ligetil at implementere. Prøv at få scriptet rent efter sig selv, og sørg for at hente alle rækker eller frigøre alle forespørgselsressourcer, fange alle undtagelser og håndtere dem korrekt, eller om muligt springe vedvarende forbindelser helt over . Lad hver instans oprette sin egen forbindelse eller brug en smart pool driver (i PHP PDO, brug PDO::ATTR_PERSISTENT udtrykkeligt sat til false ). Alternativt (f.eks. i PHP) kan du få destruct- og undtagelsesbehandlere til at tvinge til at rense forbindelsen ved at begå eller rulle tilbage transaktioner og udstede eksplicitte tabeloplåsninger.

Jeg kender ikke til en måde at forespørge efter eksisterende ressourcer i resultatsæt for at frigøre dem; den eneste måde ville være at gemme disse ressourcer i et privat array.



  1. Valg af data fra to forskellige servere i SQL Server

  2. SQL - Sådan vælger du en række med en kolonne med maks. værdi

  3. Sådan sletter du et SQL Server Agent-job i Azure Data Studio

  4. MySQL grænseområde