Munca la performanța bazei de date începe de obicei cu cineva care propune o instanță mai mare și se termină de obicei cu descoperirea că o singură interogare făcea o scanare secvențială peste 4 milioane de rânduri la fiecare încărcare de pagină. Hardware-ul nu a fost niciodată constrângerea. Planul de execuție a fost.
Tiparul se repetă suficient de constant încât merită formulat ca ipoteză implicită. Când o aplicație este lentă, iar baza de date este ocupată, cauza stă aproape întotdeauna într-un număr mic de interogări anume și nu într-o lipsă generală de capacitate. O mașină mai mare ascunde problema exact atât timp cât îi trebuie tabelei să crească din nou.
Măsoară înainte să schimbi ceva. Optimizarea unei interogări pe care ai ghicit-o este felul în care echipele pierd o săptămână adăugând indecși care încetinesc scrierile fără să grăbească nicio citire. Orice bază de date îți poate spune ce instrucțiuni consumă cel mai mult timp total. Începe de acolo, repară prima din listă, măsoară din nou. Două sau trei iterații de acest fel închid de regulă incidentul.
Performanța bazei de date începe cu găsirea interogării
Timpul total contează mai mult decât cazul cel mai rău. O interogare care durează două secunde și rulează de două ori pe zi este irelevantă. O interogare care durează patruzeci de milisecunde și rulează de 8.000 de ori pe minut este problema ta, iar ea nu va apărea niciodată într-un jurnal de interogări lente cu prag de o secundă.
În Postgres, extensia pg_stat_statements agregă exact acest lucru: apeluri, timp total și timp mediu pe instrucțiune normalizată. Sortează după timp total, iar vinovatul se află de obicei în primele trei rânduri. MySQL oferă o agregare similară prin performance schema.
Două lucruri merită verificate înainte să tragi concluzia că interogarea în sine este de vină. Este lentă de fiecare dată sau doar la anumite ore, ceea ce indică mai degrabă concurență pe resurse decât un plan prost? Și este lentă izolat sau doar sub trafic paralel, ceea ce indică blocaje sau limite de conexiuni?
Citește planul în loc să ghicești
După ce ai instrucțiunea, întreabă baza de date cum intenționează să o execute. Postgres expune acest lucru prin EXPLAIN
, iar varianta importantă este EXPLAIN ANALYZE, care execută efectiv interogarea și raportează timpi reali în loc de estimări.
Trei lucruri din acea ieșire poartă cea mai mare parte a semnalului.
Scanare secvențială pe o tabelă mare. Baza de date citește fiecare rând. Pe o tabelă mică asta este corect și rapid. Pe una mare înseamnă că nu există un index utilizabil pentru condiția pe care ai scris-o sau că planificatorul a decis că indexul nu merită folosit.
O diferență mare între numărul de rânduri estimat și cel real. Planificatorul își alege strategia din statistici, așa că atunci când estimarea lui greșește cu ordine de mărime, el alege prost din motive care nu au nicio legătură cu interogarea ta. Statisticile învechite sunt o cauză frecventă și ușor de remediat.
Timp concentrat într-un singur nod. Planurile sunt arbori, iar remedierea aparține nodului care a consumat timpul. Optimizarea oricărui alt nod nu schimbă nimic.
Instinctul de a adăuga un index imediat ce vezi o scanare secvențială este adesea corect și merită totuși treizeci de secunde de reținere, pentru că motivul pentru care indexul nu este folosit contează uneori mai mult decât absența lui.
De ce indecșii nu ajută
Un index care există nu este încă un index care se folosește.
Condiția nu este sargable. Împachetarea unei coloane într-o funcție sau aplicarea unui calcul asupra ei împiedică de regulă folosirea indexului de pe acea coloană, pentru că indexul stochează valorile coloanei și nu pe cele transformate. Rescrierea condiției astfel încât coloana să rămână neatinsă redă de obicei folosirea indexului.
Ordinea coloanelor într-un index compus este greșită. Un index compus servește interogările care folosesc coloanele lui de început. Un index pe o coloană și apoi pe alta nu ajută o interogare care filtrează doar după a doua, iar în capcana asta se cade constant.
Planificatorul consideră că o scanare costă mai puțin. Dacă o interogare returnează o fracțiune mare din tabelă, citirea secvențială chiar este mai rapidă decât săritul prin index. Acesta este comportament corect, iar remedierea înseamnă să returnezi mai puțin.
Statisticile sunt învechite. După o încărcare masivă sau o ștergere mare, imaginea pe care planificatorul o are despre date poate fi grav greșită până când statisticile sunt reîmprospătate.
Și fiecare index costă ceva. Scrierile trebuie să îl întrețină, iar el ocupă memorie care altfel ar ține date în cache. O tabelă cu cincisprezece indecși are de obicei câțiva de care nu are nimeni nevoie, fiecare făcând orice inserare mai lentă.
Problema N+1 rămâne cea mai mare cauză singulară
Mai multă lentoare în aplicații vine de aici decât din orice problemă de plan, iar ea nu apare niciodată ca interogare lentă pentru că fiecare interogare în parte este rapidă.
Forma este cunoscută. Aduci o listă de 100 de înregistrări, apoi iterezi peste ele și aduci datele asociate pentru fiecare. Rezultatul sunt 101 drumuri dus-întors acolo unde unul sau două ar fi fost de ajuns. Fiecare interogare se întoarce în trei milisecunde, iar pagina tot durează o jumătate de secundă, pentru că prețul stă în drumurile pe rețea și nu în muncă.
Maparea obiect-relațională face acest lucru ușor de scris din greșeală, fiindcă accesul la datele asociate arată ca o citire de proprietate și nu ca un apel către baza de date. Remedierea înseamnă să încarci datele asociate într-o singură interogare, împreună cu mulțimea părinte, lucru pe care orice ORM matur îl suportă și pe care majoritatea nu îl fac implicit.
Detectarea este simplă: numără interogările pe cerere. Dacă o pagină emite un număr de interogări proporțional cu numărul de elemente afișate, ai găsit-o. Este totodată verificarea cu cea mai mare valoare atunci când o aplicație este lentă la margine, așa cum arată ghidul nostru despre Cloudflare Hyperdrive , pentru că drumurile pe rețea costă mult mai mult când distanța crește.
Conexiuni și concurență
Două probleme care arată ca lentoare fără să fie.
Epuizarea conexiunilor. Orice bază de date are un plafon de conexiuni simultane, iar fiecare costă memorie. Când o aplicație deschide mai multe decât permite pool-ul, cererile stau la coadă așteptând o conexiune, iar aplicația pare lentă în timp ce baza de date stă degeaba. Simptomul este latență mare în aplicație cu procesor puțin folosit pe baza de date, iar remedierea se numește pooling, nu mașină mai mare.
Concurența pe blocaje. O tranzacție lungă care ține un blocaj oprește tot ce așteaptă în spatele ei. Cauza obișnuită este o tranzacție lăsată deschisă peste o muncă ce nu are nevoie de baza de date, de exemplu un apel HTTP către alt serviciu. Ține tranzacțiile scurte și limitate strict la munca pe baza de date.
Ambele cazuri merită excluse devreme, pentru că ambele sunt ușor de citit greșit ca problemă de interogare și niciunul nu se rezolvă cu un index.
Ce ai de făcut, în ordine
Găsește instrucțiunile care consumă cel mai mult timp total. Rulează EXPLAIN ANALYZE pe cea mai rea și citește unde se duce de fapt timpul. Verifică numărul de interogări pe cerere ca să excluzi N+1 înainte să optimizezi orice. Reîmprospătează statisticile înainte să adaugi un index, pentru că uneori asta este toată remedierea. Apoi adaugă cel mai îngust index care servește condiția și măsoară din nou.
Mecanik face asta ca parte din munca noastră de dezvoltare software , iar rezultatul este aproape mereu același: două sau trei interogări erau responsabile, remedierea a fost mică, iar instanța mai mare pe care nu o cumpărase nimeni nu a fost niciodată necesară.
Articole similare: Versionarea API: când să rupi compatibilitatea și cum să nu o faci , Cum să construiești o aplicație web în 2026 - Ghid UK , Stocarea parolelor: ce să folosești în 2026 , Dezvoltare software personalizat in Marea Britanie .
Întrebări frecvente
Cum aflu care interogare îmi încetinește aplicația? Sortează după timp total, nu după cazul cel mai rău. O interogare de patruzeci de milisecunde care rulează de 8.000 de ori pe minut costă mult mai mult decât una de două secunde care rulează de două ori pe zi, iar ea nu va apărea niciodată într-un jurnal de interogări lente cu prag de o secundă. În Postgres, pg_stat_statements agregă apelurile și timpul total pe instrucțiune, iar MySQL oferă același lucru prin performance schema.
La ce să mă uit în ieșirea EXPLAIN ANALYZE? Trei lucruri poartă cea mai mare parte a semnalului: o scanare secvențială pe o tabelă mare, adică lipsa unui index utilizabil; o diferență mare între rândurile estimate și cele reale, adică un planificator care lucrează cu statistici proaste; și timp concentrat într-un singur nod al arborelui de plan, acolo unde aparține remedierea. Optimizarea oricărui alt nod nu schimbă nimic.
De ce nu îmi este folosit indexul? De obicei dintr-unul din patru motive. Condiția împachetează coloana într-o funcție sau într-un calcul, așa că indexul nu se mai potrivește. Indexul compus are coloanele într-o ordine care nu servește interogarea. Interogarea returnează o fracțiune din tabelă suficient de mare încât o scanare chiar costă mai puțin. Sau statisticile sunt învechite după o încărcare masivă ori o ștergere.
Ce este problema de interogări N+1? Aduci o listă de înregistrări, apoi emiți o interogare separată pentru datele asociate fiecăreia, ceea ce produce 101 drumuri dus-întors acolo unde unul sau două ar fi fost de ajuns. Nu apare în niciun jurnal de interogări lente, fiindcă fiecare interogare este rapidă; prețul stă în drumurile pe rețea. O detectezi numărând interogările pe cerere și căutând un număr proporțional cu elementele afișate.
Rezolvă un server de baze de date mai mare interogările lente? Rar și doar temporar. Când o aplicație este lentă, iar baza de date este ocupată, cauza stă aproape întotdeauna într-un număr mic de interogări anume și nu într-o lipsă de capacitate, așa că o mașină mai mare ascunde problema până când tabela crește din nou. Latența mare în aplicație cu procesor puțin folosit pe baza de date indică de obicei un pool de conexiuni epuizat, iar hardware-ul suplimentar nu rezolvă asta.
Comentarii