sql >> Database >  >> RDS >> Mysql

MySQL:permanent wachten op vergrendeling van tabelmetagegevens

De geaccepteerde oplossing is helaas fout . Het is juist voor zover het zegt,

Dit is inderdaad (bijna zeker; zie hieronder) wat u moet doen. Maar dan suggereert het,

...en 1398 is niet de verbinding met het slot. Hoe kan dat nou? 1398 is de verbinding wacht voor het slot. Dit betekent dat het nog geen . heeft het slot, en daarom heeft het doden ervan niets. Het proces dat de vergrendeling vasthoudt, houdt de vergrendeling nog steeds vast, en de volgende thread die iets probeert te doen, zal daarom ook blokkeer en voer in de juiste volgorde "Wachten op metadatavergrendeling" in.

U hebt geen garantie dat de processen "wachten op metadatavergrendeling" (WFML) niet ook worden geblokkeerd, maar u kunt er zeker van zijn dat het doden van alleen WFML-processen precies niets oplevert .

De echte oorzaak is dat een ander proces de vergrendeling vasthoudt , en nog belangrijker, SHOW FULL PROCESSLIST zal je niet direct vertellen welke het is .

Het ZAL u vertellen of het proces doet iets, ja. Meestal werkt het. Hier doet het proces dat het slot vasthoudt niets , en verbergt zich tussen andere threads die ook niets doen.

In dit geval is de boosdoener vrijwel zeker proces 1396 , die begon vóór proces 1398 en nu in Sleep . is staat, en is 46 seconden geweest. Sinds 1396 duidelijk alles deed wat het moest doen (zoals blijkt uit het feit dat het nu slaapt, en dit gedurende 46 seconden heeft gedaan, voor zover het MySQL betreft ), geen thread die eerder in slaap was gevallen, had een slot kunnen bevatten (of 1396 zou ook zijn vastgelopen).

BELANGRIJK :als je als beperkte gebruiker verbinding hebt gemaakt met MySQL, SHOW FULL PROCESSLIST zal niet laat alle processen zien. Het slot kan dus vastgehouden worden door een proces dat je niet ziet.

Een betere 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

Het bovenstaande kan worden afgestemd om alleen de processen in SLEEP-status weer te geven, en hoe dan ook, het zal ze sorteren op tijd aflopend, dus het is gemakkelijker om het proces te vinden dat vastzit (het is meestal de Sleep er een direct voor degenen "wachten op metadata lock").

Het belangrijkste

Laat elk proces van "wachten op metadatavergrendeling" met rust .

Snelle en vuile oplossing, niet echt aanbevolen maar snel

Dood allen processen in "Slaap"-status, in dezelfde database, die ouder zijn dan de oudste thread in de status "wachten op metagegevensvergrendeling". Dit is wat Arnaud Amaury zou hebben gedaan:

  • voor elke database met ten minste één thread in WaitingForMetadataLock:
    • de oudste verbinding in WFML op die DB blijkt Z seconden oud te zijn
    • ALLE "Slaap"-threads op die DB en ouder dan Z moeten verdwijnen. Begin met de meest verse, voor het geval dat.
    • Als er een oudere en niet-slapende verbinding bestaat op die DB, dan is dat misschien degene die het slot vasthoudt, maar het doet iets . Je mag het natuurlijk doden, maar vooral als het een UPDATE/INSERT/DELETE is, doe je dit op eigen risico.

Negenennegentig van de honderd keer is de draad die gedood moet worden de jongste onder degenen in de slaapstand die ouder zijn dan de oudere die wacht op metadatavergrendeling:

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

(*) de TIME-volgorde heeft eigenlijk milliseconden, zo werd mij verteld, het laat ze gewoon niet zien. Dus hoewel beide processen een tijdwaarde van 19 hebben, zou de laagste jonger moeten zijn.

Meer gerichte oplossing

Voer SHOW ENGINE INNODB STATUS uit en kijk naar de sectie "TRANSACTIE". U vindt er onder andere zoiets als

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 controleer je met SHOW FULL PROCESSLIST wat doet thread id 1396 met zijn #1701 transactie. De kans is groot dat het de status "Slaap" heeft. Dus:een actieve transactie (#1701) met een actieve vergrendeling, het heeft zelfs enkele wijzigingen aangebracht omdat het een log-invoer voor ongedaan maken heeft... maar is momenteel inactief. Dat en geen ander is de draad die je moet doden. Die wijzigingen kwijtraken.

Onthoud dat niets doen in MySQL niet betekent dat je in het algemeen niets doet. Als u enkele records uit MySQL haalt en een CSV voor FTP-upload opbouwt, is tijdens de FTP-upload de MySQL-verbinding inactief.

Als het proces dat MySQL en de MySQL-server gebruikt zich op dezelfde machine bevinden, op die machine Linux draait en u rootrechten hebt, is er een manier om erachter te komen welk proces heeft de verbinding die het slot heeft aangevraagd. Dit maakt het op zijn beurt mogelijk om te bepalen (van het CPU-gebruik of, in het slechtste geval, strace -ff -p pid ) of dat proces echt . is iets doen of niet, om te helpen beslissen of het veilig is om te doden.

Waarom gebeurt dit?

Ik zie dit gebeuren met webapps die gebruik maken van "permanente" of "gepoolde" MySQL-verbindingen, die tegenwoordig meestal weinig tijd besparen:de webapp-instantie is beëindigd, maar de verbinding niet , dus het slot is nog steeds in leven... en blokkeert alle anderen.

Een andere interessante manier dat ik vond, is om, in de hypothesen hierboven, een query uit te voeren die enkele rijen retourneert, en er slechts enkele ophaalt . Als de query niet is ingesteld op "auto-clean" (hoe de onderliggende DBA dit ook doet), blijft de verbinding open en wordt voorkomen dat de tafel volledig wordt vergrendeld. Ik heb dit laten gebeuren in een stukje code dat verifieerde of een rij bestond door die rij te selecteren en te verifiëren of deze een fout kreeg (bestaat niet) of niet (het moet bestaan), maar zonder de rij daadwerkelijk op te halen .

Vraag het aan de database

Een andere manier om de boosdoener te achterhalen als je een recente MySQL hebt, maar niet te recent aangezien dit wordt beëindigd , is (u hebt opnieuw privileges nodig op het informatieschema)

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

Werkelijke oplossing, kost tijd en werk

Het probleem wordt meestal veroorzaakt door deze architectuur:

Wanneer de webapp sterft, of de instantie van de lichtgewicht thread van de webapp sterft, de container/verbindingspool mogelijk niet . En het is de container dat houdt de verbinding open, dus de verbinding sluit duidelijk niet. Heel voorspelbaar, MySQL beschouwt de bewerking niet als voltooid .

Als de webapp zichzelf niet heeft opgeschoond (geen ROLLBACK of COMMIT voor een transactie, geen UNLOCK TABLES , etc.), dan is alles wat die webapp begon te doen bestaat nog steeds , en blokkeert mogelijk nog steeds alle anderen.

Er zijn dan twee oplossingen. De ergste is om de time-out voor inactiviteit te verlagen . Maar raad eens wat er gebeurt als je te lang wacht tussen twee vragen (precies:"MySQL-server is verdwenen"). Je zou dan mysql_ping . kunnen gebruiken indien beschikbaar (binnenkort te beëindigen. Er zijn oplossingen voor PDO. Of je zou kunnen controleren op dat fout, en heropen de verbinding als dit gebeurt (dit is de Python-manier). Dus - voor een kleine prestatievergoeding - is het te doen.

De betere, slimmere oplossing is minder eenvoudig te implementeren. Probeer het script na zichzelf op te schonen, ervoor te zorgen dat alle rijen worden opgehaald of alle querybronnen worden vrijgemaakt, alle uitzonderingen worden opgevangen en correct worden afgehandeld, of, indien mogelijk, persistente verbindingen helemaal overslaan . Laat elke instantie zijn eigen verbinding maken of gebruik een slimme zwembad chauffeur (gebruik in PHP PDO PDO::ATTR_PERSISTENT expliciet ingesteld op false ). Als alternatief (bijv. in PHP) kunt u destruct- en exception-handlers de verbinding geforceerd opschonen door transacties te plegen of terug te draaien en expliciete tabelontgrendelingen uit te geven.

Ik ken geen manier om bestaande bronnen voor resultatensets op te vragen om ze vrij te maken; de enige manier zou zijn om op te slaan die bronnen in een privé-array.



  1. mysql-queryresultaat in php-array

  2. MySQL:DATETIME gebruiken als primaire sleutel

  3. 4 functies die microseconden extraheren uit een tijdwaarde in MariaDB

  4. Hoe MySQL-edelsteen op Mac OS X te installeren