Incidentu reaģēšana

MySQL "Lock wait timeout exceeded" (kļūda 1205): atrodiet bloķētāju un novērsiet to

ERROR 1205 vēsta, ka vaicājums 50 sekundes gaidīja rindu un padevās. Tā nesaka, kurš šo rindu turēja. Lūk, kā atrast bloķējošo transakciju, ko ar to darīt un kā panākt, lai tas neatkārtotos.

· 12 min lasīšanai · AG Data komanda

MySQL "Lock wait timeout exceeded" ir kļūda, ko saņemat, kad vaicājums pārāk ilgi gaidījis rindu, ko tur cita transakcija. Ziņojums ir ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction, un tajā dotais padoms ir tikai daļēji pareizs. Transakcijas atkārtošana var izdoties. Bet, ja kaut kas sēž uz šīm rindām, atkārtojums gaidīs vēl 50 sekundes un izgāzīsies tieši tāpat. Noderīgais jautājums nav "kā likt kļūdai pazust", bet "kurš tur bloķējumu un kāpēc tik ilgi?"

Kas patiesībā notika

InnoDB bloķē rindas, ko transakcija maina, un rindas, kas nolasītas ar SELECT ... FOR UPDATE vai FOR SHARE. Šie bloķējumi paliek, līdz transakcija tiek apstiprināta vai atritināta. Citai transakcijai, kas grib rakstīt to pašu rindu, jāgaida. innodb_lock_wait_timeout nosaka, cik ilgi: pēc noklusējuma 50 sekundes. Pēc tam gaidošais vaicājums saņem kļūdu 1205.

Trīs detaļas bieži pārsteidz:

1. solis: atrodiet bloķētāju, kamēr tas notiek

sys shēmā ir skats, kas radīts tieši tam. sys.innodb_lock_waits parāda katru gaidošo transakciju, ko tā gaida un kura sesija to bloķē:

SELECT wait_age_secs, locked_table_name, locked_index,
       waiting_pid, waiting_query,
       blocking_pid, blocking_query, sql_kill_blocking_connection
FROM sys.innodb_lock_waits
ORDER BY wait_age_secs DESC;

Kolonna blocking_pid ir bloķējošās sesijas procesu saraksta ID, un sql_kill_blocking_connection satur gatavu KILL komandu tai. Pirms to izpildāt, paskatieties uz blocking_query. Ja tas ir NULL, bloķējošā sesija šobrīd neko neizpilda. Tas ir biežākais gadījums un svarīgākā norāde: transakcija, kas sākās, mainīja vai bloķēja dažas rindas un tad apklusa bez commit.

Ja vairākas sesijas bloķē viena otru ķēdē, meklējiet to, kas ir ķēdes sākumā: tā bloķē citas, bet pati neko negaida. Sesiju pārtraukšana ķēdes vidū nepalīdz.

2. solis: apskatiet vecākās atvērtās transakcijas

Pat ja šobrīd nekas negaida, ilgi atvērtas transakcijas ir vieta, no kurienes nāks nākamais taimauts. information_schema.innodb_trx uzskaita katru darbojošos InnoDB transakciju:

SELECT trx_mysql_thread_id AS pid, trx_started,
       TIMESTAMPDIFF(SECOND, trx_started, NOW()) AS age_sec,
       trx_rows_locked, trx_rows_modified, trx_query
FROM information_schema.innodb_trx
ORDER BY trx_started
LIMIT 10;

Transakcija, kas ir minūtes vai stundas veca un kurai trx_query ir NULL, gandrīz vienmēr nozīmē lietotnes kodu, kas atvēra transakciju un nekad to neaizvēra. Tipiski vaininieki:

3. solis: izlemiet, ko pārtraukt

Bloķētāja pārtraukšana atbrīvo tā bloķējumus, bet arī atritina visu, ko šī transakcija paveikusi. Neaktīvai sesijai, kas aizmirsa commit, tas parasti ir pieņemami, un tas ir ātrākais ceļš ārā. Lielam pakešuzdevumam, kas rindas mainījis divdesmit minūtes, atritināšana var ilgt ilgi un visu šo laiku turēt bloķējumus. Tādā gadījumā var būt ātrāk apturēt jauna darba uzkrāšanos un ļaut pakešuzdevumam pabeigt.

Nepārtrauciet gaidošās sesijas. Tās ir upuri, nevis cēlonis. Katru no tām aizstās jauns pieprasījums, kas gaidīs uz tām pašām rindām. Un, ja gaidīšanas krājas pietiekami ātri, nākamā kļūda būs "Too many connections"; tam skatiet mūsu rīcības plānu kļūdai 1040.

innodb_lock_wait_timeout palielināšana ir biežākais "risinājums" un reti pareizais. Tā neko nepaātrina. Tā tikai nozīmē, ka katrs gaidošais pieprasījums ilgāk aizņem savienojumu, pavedienu un lietotāju, pirms izgāžas. Interaktīvām lietotnēm MySQL rokasgrāmata pat iesaka pretējo virzienu: mazāku vērtību, lai lietotāji ātri saņemtu atbildi. Lielāka vērtība ir jēgpilna gariem fona uzdevumiem, no kuriem gaidīšana ir sagaidāma, un to var iestatīt sesijas līmenī tikai šiem uzdevumiem.

Kāpēc transakcijas tur bloķējumus ilgāk, nekā domājat

Daži cēloņi ir mazāk acīmredzami nekā aizmirsts commit:

Kā panākt, lai tas notiktu reti

  1. Turiet transakcijas īsas. Rokasgrāmatas padoms ir veikt pēc iespējas mazāk darba starp pirmo izmaiņu un commit. Lēno darbu, piemēram, API izsaukumus, failu operācijas un aprēķinus, veiciet pirms transakcijas atvēršanas vai pēc commit.
  2. Atkārtojiet visu transakciju, nevis tikai vaicājumu. Bloķējumu gaidīšanas un strupceļi ir normāli noslogotā serverī, un lietotnei tie jāparedz. Noķeriet 1205 un 1213, skaidri atritiniet, pagaidiet īsu nejaušu intervālu un palaidiet visu transakciju vēlreiz ierobežotu reižu skaitu.
  3. Indeksējiet kolonnas, pēc kurām filtrējat UPDATE, DELETE un SELECT ... FOR UPDATE. Mazāk skenētu rindu nozīmē mazāk bloķējumu.
  4. Rindām izmantojiet NOWAIT vai SKIP LOCKED. Darbiniekiem, kas ņem uzdevumus no tabulas, nevajadzētu stāvēt rindā vienam aiz otra:
    START TRANSACTION;
    SELECT id FROM jobs
    WHERE status = 'queued'
    ORDER BY id
    LIMIT 10
    FOR UPDATE SKIP LOCKED;
    -- process the rows, then
    COMMIT;
  5. Brīdiniet par vecām transakcijām. OLTP sistēmā transakcija, kas vecāka par dažām minūtēm, ir paziņojuma vērta ilgi pirms tā pārvēršas taimautu vētrā. Augstāk dotais innodb_trx vaicājums ir gatava pārbaude.

Galvenās atziņas

MySQL InnoDB Bloķēšana Incidentu reaģēšana
Sadarbība ar mums

MySQL krājas bloķējumu gaidīšanas?

Mēs atrodam, kas jūsu MySQL vai MariaDB datubāzē tur bloķējumus, stabilizējam produkciju un salabojam transakciju modeļus, kas to izraisa. Pirmā reakcija uz kritiskiem incidentiem 15 minūšu laikā, 24/7.

Sazināties Vairāk no emuāra