Optimizuokite Django duomenų bazės užklausas su select_related ir prefetch_related, kad padidintumėte našumą. Sužinokite praktinius pavyzdžius ir geriausias praktikas.
Django ORM užklausų optimizavimas: select_related vs. prefetch_related
Augant jūsų Django aplikacijai, efektyvios duomenų bazės užklausos tampa lemiamos norint išlaikyti optimalų našumą. Django ORM suteikia galingus įrankius, leidžiančius sumažinti kreipinių į duomenų bazę skaičių ir pagerinti užklausų greitį. Dvi pagrindinės technikos tai pasiekti yra select_related ir prefetch_related. Šis išsamus vadovas paaiškins šias koncepcijas, pademonstruos jų naudojimą su praktiniais pavyzdžiais ir padės jums pasirinkti tinkamą įrankį konkretiems poreikiams.
N+1 problemos supratimas
Prieš pradedant nagrinėti select_related ir prefetch_related, būtina suprasti problemą, kurią jie sprendžia: N+1 užklausų problemą. Tai įvyksta, kai jūsų aplikacija įvykdo vieną pradinę užklausą objektų rinkiniui gauti, o tada atlieka papildomas užklausas (N užklausų, kur N yra objektų skaičius), kad gautų susijusius duomenis kiekvienam objektui.
Panagrinėkime paprastą pavyzdį su modeliais, reprezentuojančiais autorius ir knygas:
class Author(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
Dabar įsivaizduokite, kad norite parodyti knygų sąrašą su atitinkamais autoriais. Naivus požiūris galėtų atrodyti taip:
books = Book.objects.all()
for book in books:
print(f"{book.title} by {book.author.name}")
Šis kodas sugeneruos vieną užklausą visoms knygoms gauti, o tada po vieną užklausą kiekvienai knygai, kad gautų jos autorių. Jei turite 100 knygų, įvykdysite 101 užklausą, kas sukels didelį našumo sumažėjimą. Tai ir yra N+1 problema.
Pristatome select_related
select_related yra naudojamas optimizuoti užklausas, apimančias „vienas su vienu“ (one-to-one) ir svetimo rakto (foreign key) ryšius. Jis veikia sujungdamas susijusią(-ias) lentelę(-es) pradinėje užklausoje, taip efektyviai gaudamas susijusius duomenis vienu kreipiniu į duomenų bazę.
Grįžkime prie mūsų autorių ir knygų pavyzdžio. Norėdami pašalinti N+1 problemą, galime naudoti select_related taip:
books = Book.objects.all().select_related('author')
for book in books:
print(f"{book.title} by {book.author.name}")
Dabar Django įvykdys vieną, sudėtingesnę užklausą, kuri sujungs Book ir Author lenteles. Kai cikle kreipsitės į book.author.name, duomenys jau bus pasiekiami ir papildomos duomenų bazės užklausos nebus atliekamos.
select_related naudojimas su keliais ryšiais
select_related gali pereiti per kelis ryšius. Pavyzdžiui, jei turite modelį su svetimu raktu į kitą modelį, kuris savo ruožtu turi svetimą raktą į dar kitą modelį, galite naudoti select_related, kad gautumėte visus susijusius duomenis vienu ypu.
class Country(models.Model):
name = models.CharField(max_length=255)
class AuthorProfile(models.Model):
author = models.OneToOneField(Author, on_delete=models.CASCADE)
country = models.ForeignKey(Country, on_delete=models.CASCADE)
# Add country to Author
Author.profile = models.OneToOneField(AuthorProfile, on_delete=models.CASCADE, null=True, blank=True)
authors = Author.objects.all().select_related('profile__country')
for author in authors:
print(f"{author.name} is from {author.profile.country.name if author.profile else 'Unknown'}")
Šiuo atveju select_related('profile__country') gauna AuthorProfile ir susijusį Country viena užklausa. Atkreipkite dėmesį į dvigubo pabraukimo (__) žymėjimą, kuris leidžia jums keliauti per ryšių medį.
select_related apribojimai
select_related yra efektyviausias su „vienas su vienu“ ir svetimo rakto ryšiais. Jis netinka „daugelis su daugeliu“ (many-to-many) arba atvirkštiniams svetimo rakto ryšiams, nes tai gali sukelti dideles ir neefektyvias užklausas dirbant su dideliais susijusių duomenų rinkiniais. Tokiems scenarijams geresnis pasirinkimas yra prefetch_related.
Pristatome prefetch_related
prefetch_related yra sukurtas optimizuoti užklausas, apimančias „daugelis su daugeliu“ (many-to-many) ir atvirkštinius svetimo rakto (reverse foreign key) ryšius. Užuot naudojęs sujungimus (joins), prefetch_related atlieka atskiras užklausas kiekvienam ryšiui ir tada naudoja Python, kad „sujungtų“ rezultatus. Nors tai apima kelias užklausas, tai gali būti efektyviau nei naudoti sujungimus dirbant su dideliais susijusių duomenų rinkiniais.
Apsvarstykite scenarijų, kai kiekviena knyga gali turėti kelis žanrus:
class Genre(models.Model):
name = models.CharField(max_length=255)
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author, on_delete=models.CASCADE)
genres = models.ManyToManyField(Genre)
Norint gauti knygų sąrašą su jų žanrais, naudoti select_related nebūtų tinkama. Vietoj to, naudojame prefetch_related:
books = Book.objects.all().prefetch_related('genres')
for book in books:
genre_names = [genre.name for genre in book.genres.all()]
print(f"{book.title} ({', '.join(genre_names)}) by {book.author.name}")
Šiuo atveju Django įvykdys dvi užklausas: vieną visoms knygoms gauti ir kitą – visiems žanrams, susijusiems su tomis knygomis, gauti. Tada jis naudoja Python, kad efektyviai susietų žanrus su atitinkamomis knygomis.
prefetch_related su atvirkštiniais svetimais raktais
prefetch_related taip pat naudingas optimizuojant atvirkštinius svetimo rakto ryšius. Apsvarstykite šį pavyzdį:
class Author(models.Model):
name = models.CharField(max_length=255)
country = models.CharField(max_length=255, blank=True, null=True) # Added for clarity
def __str__(self):
return self.name
class Book(models.Model):
title = models.CharField(max_length=255)
author = models.ForeignKey(Author, related_name='books', on_delete=models.CASCADE)
Norėdami gauti autorių sąrašą ir jų knygas:
authors = Author.objects.all().prefetch_related('books')
for author in authors:
book_titles = [book.title for book in author.books.all()]
print(f"{author.name} has written: {', '.join(book_titles)}")
Čia prefetch_related('books') atskira užklausa gauna visas knygas, susijusias su kiekvienu autoriumi, taip išvengiant N+1 problemos kreipiantis į author.books.all().
prefetch_related naudojimas su užklausų rinkiniu (queryset)
Galite dar labiau pritaikyti prefetch_related elgseną, pateikdami pasirinktinį užklausų rinkinį (queryset) susijusiems objektams gauti. Tai ypač naudinga, kai reikia filtruoti ar rikiuoti susijusius duomenis.
from django.db.models import Prefetch
authors = Author.objects.prefetch_related(Prefetch('books', queryset=Book.objects.filter(title__icontains='django')))
for author in authors:
django_books = author.books.all()
print(f"{author.name} has written {len(django_books)} books about Django.")
Šiame pavyzdyje Prefetch objektas leidžia mums nurodyti pasirinktinį užklausų rinkinį, kuris gauna tik tas knygas, kurių pavadinimuose yra „django“.
prefetch_related grandininis jungimas
Panašiai kaip ir select_related, galite sujungti prefetch_related iškvietimus, kad optimizuotumėte kelis ryšius:
authors = Author.objects.all().prefetch_related('books__genres')
for author in authors:
for book in author.books.all():
genres = book.genres.all()
print(f"{author.name} wrote {book.title} which is of genre(s) {[genre.name for genre in genres]}")
Šis pavyzdys iš anksto gauna knygas, susijusias su autoriumi, o tada – žanrus, susijusius su tomis knygomis. Naudojant grandininį prefetch_related, galite optimizuoti giliai įdėtus ryšius.
select_related vs. prefetch_related: tinkamo įrankio pasirinkimas
Taigi, kada turėtumėte naudoti select_related ir kada prefetch_related? Štai paprasta gairė:
select_related: Naudokite „vienas su vienu“ ir svetimo rakto ryšiams, kai dažnai reikia pasiekti susijusius duomenis. Jis atlieka sujungimą (join) duomenų bazėje, todėl paprastai yra greitesnis gaunant nedidelį kiekį susijusių duomenų.prefetch_related: Naudokite „daugelis su daugeliu“ ir atvirkštiniams svetimo rakto ryšiams, arba dirbant su dideliais susijusių duomenų rinkiniais. Jis atlieka atskiras užklausas ir naudoja Python rezultatams sujungti, kas gali būti efektyviau nei dideli sujungimai. Taip pat naudokite, kai reikia taikyti pasirinktinį užklausų rinkinio filtravimą susijusiems objektams.
Apibendrinant:
- Ryšio tipas:
select_related(ForeignKey, OneToOne),prefetch_related(ManyToManyField, atvirkštinis ForeignKey) - Užklausos tipas:
select_related(JOIN),prefetch_related(atskiros užklausos + Python sujungimas) - Duomenų dydis:
select_related(maži susiję duomenys),prefetch_related(dideli susiję duomenys)
Praktiniai pavyzdžiai ir geriausios praktikos
Štai keletas praktinių pavyzdžių ir geriausių praktikų, kaip naudoti select_related ir prefetch_related realiuose scenarijuose:
- El. prekyba: Rodydami produkto informaciją, naudokite
select_related, kad gautumėte produkto kategoriją ir gamintoją. Naudokiteprefetch_related, kad gautumėte produkto paveikslėlius ar susijusius produktus. - Socialiniai tinklai: Rodydami vartotojo profilį, naudokite
prefetch_related, kad gautumėte vartotojo įrašus ir sekėjus. Naudokiteselect_related, kad gautumėte vartotojo profilio informaciją. - Turinio valdymo sistema (TVS): Rodydami straipsnį, naudokite
select_related, kad gautumėte autorių ir kategoriją. Naudokiteprefetch_related, kad gautumėte straipsnio žymes ir komentarus.
Bendrosios geriausios praktikos:
- Profiluokite savo užklausas: Naudokite Django derinimo įrankių juostą (debug toolbar) ar kitus profiliavimo įrankius, kad nustatytumėte lėtas užklausas ir galimas N+1 problemas.
- Pradėkite paprastai: Pradėkite nuo naivios implementacijos ir optimizuokite remdamiesi profiliavimo rezultatais.
- Kruopščiai testuokite: Įsitikinkite, kad jūsų optimizacijos neįveda naujų klaidų ar našumo regresijų.
- Apsvarstykite podėliavimą (caching): Dažnai pasiekiamiems duomenims apsvarstykite galimybę naudoti podėliavimo mechanizmus (pvz., Django podėliavimo sistemą ar Redis), kad dar labiau pagerintumėte našumą.
- Naudokite indeksus duomenų bazėje: Tai būtina optimaliam užklausų našumui, ypač produkcinėje aplinkoje.
Pažangios optimizavimo technikos
Be select_related ir prefetch_related, yra ir kitų pažangių technikų, kurias galite naudoti savo Django ORM užklausoms optimizuoti:
only()irdefer(): Šie metodai leidžia nurodyti, kuriuos laukus gauti iš duomenų bazės. Naudokiteonly(), kad gautumėte tik būtinus laukus, odefer()– kad išskirtumėte laukus, kurie nėra iš karto reikalingi.values()irvalues_list(): Šie metodai leidžia gauti duomenis kaip žodynus ar rinkinius (tuples), o ne Django modelių egzempliorius. Tai gali būti efektyviau, kai jums reikia tik dalies modelio laukų.- Grynos SQL užklausos: Kai kuriais atvejais Django ORM gali būti ne pats efektyviausias būdas gauti duomenis. Galite naudoti grynas SQL užklausas sudėtingoms ar labai optimizuotoms užklausoms.
- Specifinės duomenų bazės optimizacijos: Skirtingos duomenų bazės (pvz., PostgreSQL, MySQL) turi skirtingas optimizavimo technikas. Ištirkite ir pasinaudokite specifinėmis duomenų bazės funkcijomis, kad dar labiau pagerintumėte našumą.
Tarptautinimo aspektai
Kuriant Django aplikacijas pasaulinei auditorijai, svarbu atsižvelgti į tarptautinimą (i18n) ir lokalizavimą (l10n). Tai gali paveikti jūsų duomenų bazės užklausas keliais būdais:
- Kalbai specifiški duomenys: Gali tekti saugoti turinio vertimus savo duomenų bazėje. Naudokite Django i18n sistemą vertimams valdyti ir užtikrinti, kad jūsų užklausos gautų teisingą duomenų kalbos versiją.
- Simbolių rinkiniai ir rikiavimo taisyklės (Collations): Pasirinkite tinkamus simbolių rinkinius ir rikiavimo taisykles savo duomenų bazei, kad palaikytumėte platų kalbų ir simbolių spektrą.
- Laiko juostos: Dirbdami su datomis ir laikais, būkite atidūs laiko juostoms. Saugokite datas ir laikus UTC formatu ir konvertuokite juos į vartotojo vietinę laiko juostą, kai juos rodote.
- Valiutų formatavimas: Rodydami kainas, naudokite tinkamus valiutų simbolius ir formatavimą, atsižvelgiant į vartotojo lokalę.
Išvada
Django ORM užklausų optimizavimas yra būtinas norint kurti mastelį atlaikančias ir našias interneto aplikacijas. Suprasdami ir efektyviai naudodami select_related bei prefetch_related, galite ženkliai sumažinti duomenų bazės užklausų skaičių ir pagerinti bendrą jūsų aplikacijos reakcijos greitį. Nepamirškite profiliuoti savo užklausų, kruopščiai testuoti optimizacijas ir apsvarstyti kitas pažangias technikas, kad dar labiau padidintumėte našumą. Laikydamiesi šių geriausių praktikų, galite užtikrinti, kad jūsų Django aplikacija suteiks sklandžią ir efektyvią vartotojo patirtį, nepriklausomai nuo jos dydžio ar sudėtingumo. Taip pat atsižvelkite į tai, kad geras duomenų bazės dizainas ir tinkamai sukonfigūruoti indeksai yra būtini optimaliam našumui.