Optimering av databasåtkomst¶
Djangos databaslager tillhandahåller olika sätt att hjälpa utvecklare att få ut mesta möjliga av sina databaser. Detta dokument samlar länkar till relevant dokumentation och lägger till olika tips, organiserade under ett antal rubriker som beskriver de steg som ska vidtas när du försöker optimera din databasanvändning.
Profil först¶
Som allmän programmeringspraxis säger detta sig självt. Ta reda på vilka frågor du gör och vad de kostar dig. Använd QuerySet.explain() för att förstå hur specifika QuerySet exekveras av din databas. Du kanske också vill använda ett externt projekt som django-debug-toolbar, eller ett verktyg som övervakar din databas direkt.
Kom ihåg att du kan optimera för hastighet eller minne eller båda, beroende på dina krav. Ibland kan optimering för det ena vara till nackdel för det andra, men ibland kan de hjälpa varandra. Det arbete som utförs av databasprocessen kanske inte heller har samma kostnad (för dig) som samma mängd arbete som utförs i din Python-process. Det är upp till dig att bestämma vilka dina prioriteringar är, var balansen måste ligga och profilera alla dessa efter behov eftersom detta beror på din applikation och server.
Med allt som följer, kom ihåg att profilera efter varje ändring för att säkerställa att ändringen är en fördel, och en tillräckligt stor fördel med tanke på minskningen av läsbarheten i din kod. Alla förslagen nedan kommer med förbehållet att den allmänna principen kanske inte gäller under dina omständigheter, eller kanske till och med är omvänd.
Använda standardtekniker för DB-optimering¶
…inklusive..:
Index. Detta har högsta prioritet, efter att du genom profilering har fastställt vilka index som ska läggas till. Använd
Meta.indexesellerField.db_indexför att lägga till dem från Django. Överväg att lägga till index på fält som du ofta frågar efter medfilter(),exclude(),order_by()och så vidare, eftersom index kan göra uppslag snabbare. Observera att det är ett komplext, databasberoende ämne att avgöra de bästa indexen och att det beror på din specifika applikation. Omkostnaden för att underhålla ett index kan överstiga eventuella vinster i frågehastighet.
Lämplig användning av fälttyper.
Vi kommer att anta att du har gjort de saker som anges ovan. Resten av detta dokument fokuserar på hur man använder Django på ett sådant sätt att man inte gör onödigt arbete. Detta dokument tar inte heller upp andra optimeringstekniker som gäller för alla dyra operationer, till exempel general purpose caching.
Förstå QuerySet¶
Att förstå QuerySets är avgörande för att få bra prestanda med enkel kod. I synnerhet:
Förstå utvärdering av QuerySet¶
För att undvika prestandaproblem är det viktigt att förstå:
att QuerySets är lata.
när de utvärderas.
hur data lagras i minnet.
Förstå cachelagrade attribut¶
Förutom cachelagring av hela QuerySet finns det cachelagring av resultatet av attribut på ORM-objekt. I allmänhet kommer attribut som inte är anropsbara att cachelagras. Om vi till exempel antar att example blog models:
>>> entry = Entry.objects.get(id=1)
>>> entry.blog # Blog object is retrieved at this point
>>> entry.blog # cached version, no DB access
Men i allmänhet orsakar anropsbara attribut DB-uppslagningar varje gång:
>>> entry = Entry.objects.get(id=1)
>>> entry.authors.all() # query performed
>>> entry.authors.all() # query performed again
Var försiktig när du läser mallkod - mallsystemet tillåter inte användning av parenteser, men kommer att kalla callables automatiskt, vilket döljer ovanstående distinktion.
Var försiktig med dina egna anpassade egenskaper - det är upp till dig att implementera caching när det behövs, till exempel med hjälp av cached_property-dekoratorn.
Använd malltaggen with¶
För att utnyttja cachelagringsbeteendet hos QuerySet kan du behöva använda malltaggen with.
Använd iterator()¶
När du har många objekt kan cachningsbeteendet hos QuerySet leda till hög minnesanvändning. I det fallet kan iterator() hjälpa.
Använd explain()¶
QuerySet.explain() ger dig detaljerad information om hur databasen exekverar en fråga, inklusive index och sammanfogningar som används. Dessa detaljer kan hjälpa dig att hitta frågor som kan skrivas om på ett effektivare sätt eller identifiera index som kan läggas till för att förbättra prestandan.
Gör databasarbete i databasen snarare än i Python¶
Till exempel:
På den mest grundläggande nivån använder du filter och exkludera för att göra filtrering i databasen.
Använd F()-uttryck för att filtrera utifrån andra fält i samma modell.
Om dessa inte räcker för att generera den SQL du behöver:
Använd RawSQL¶
En mindre portabel men mer kraftfull metod är RawSQL-uttrycket, som gör att viss SQL uttryckligen kan läggas till i frågan. Om det fortfarande inte är tillräckligt kraftfullt:
Använd rå SQL¶
Skriv din egen custom SQL för att hämta data eller fylla i modeller. Använd django.db.connection.queries för att ta reda på vad Django skriver åt dig och börja därifrån.
Hämta enskilda objekt med hjälp av en unik, indexerad kolumn¶
Det finns två skäl att använda en kolumn med unique eller db_index när man använder get() för att hämta enskilda objekt. För det första kommer frågan att vara snabbare på grund av det underliggande databasindexet. Dessutom kan frågan köras mycket långsammare om flera objekt matchar uppslagningen; att ha en unik begränsning på kolumnen garanterar att detta aldrig kommer att hända.
Så med hjälp av exempel på bloggmodeller:
>>> entry = Entry.objects.get(id=10)
kommer att vara snabbare än:
>>> entry = Entry.objects.get(headline="News Item Title")
eftersom id indexeras av databasen och garanterat är unik.
Att göra följande är potentiellt ganska långsamt:
>>> entry = Entry.objects.get(headline__startswith="News")
Först och främst är headline inte indexerad, vilket gör att den underliggande databashämtningen blir långsammare.
För det andra kan uppslaget returnera flera resultat. Även om Django tillämpar en gräns på 21 objekt när du använder get() kan databasen fortfarande behöva genomsöka allt för att utvärdera ett icke-unikt villkor. Det är effektivare att begränsa frågan i förväg.
Hämta inte saker som du inte behöver¶
Använd QuerySet.values() och values_list()¶
När du bara vill ha en dict eller list med värden och inte behöver ORM-modellobjekt, använd values() på lämpligt sätt. Dessa kan vara användbara för att ersätta modellobjekt i mallkod – så länge ordböckerna du tillhandahåller har samma attribut som används i mallen går det bra.
Använd QuerySet.defer() och only()¶
Använd defer() och only() om det finns databaskolumner som du vet att du inte behöver (eller inte behöver i de flesta fall), för att undvika att läsa in dem. Observera att om du faktiskt använder dem måste ORM hämta dem i en separat fråga, vilket försämrar prestandan om du använder detta olämpligt.
Var inte för aggressiv när det gäller att skjuta upp fält utan profilering eftersom databasen måste läsa det mesta av icke-text, icke-VARCHAR-data från disken för en enda rad i resultaten, även om det slutar med att bara använda några få kolumner. Metoderna defer() och only() är mest användbara när du kan undvika att ladda en massa textdata eller för fält som kan kräva mycket bearbetning för att konvertera tillbaka till Python. Som alltid, profilera först, optimera sedan.
Använd QuerySet.contains(obj)¶
…om du bara vill ta reda på om obj finns i frågeuppsättningen, snarare än om obj i frågeuppsättningen.
Använd QuerySet.count()¶
…om du bara vill ha antalet, istället för att göra len(queryset).
Använd QuerySet.exists()¶
…om du bara vill ta reda på om minst ett resultat finns, snarare än if queryset.
Men..:
Överanvänd inte contains(), count() och exists()¶
Om du kommer att behöva andra data från QuerySet ska du utvärdera dem omedelbart.
Om vi till exempel antar att en Group-modell har en många-till-många-relation till User, är följande kod optimal:
members = group.members.all()
if display_group_members:
if members:
if current_user in members:
print("You and", len(members) - 1, "other users are members of this group.")
else:
print("There are", len(members), "members in this group.")
for member in members:
print(member.username)
else:
print("There are no members in this group.")
Det är optimalt eftersom:
Eftersom QuerySets är lata görs inga databasfrågor om
display_group_membersärFalse.Genom att lagra
group.members.all()i variabelnmemberskan dess resultatcache återanvändas.Raden
if members:gör attQuerySet.__bool__()anropas, vilket gör attgroup.members.all()-frågan körs på databasen. Om det inte finns några resultat returnerasFalse, annarsTrue.Raden
if current_user in members:kontrollerar om användaren finns i resultatcachen, så att inga ytterligare databasfrågor ställs.Användningen av
len(members)anroparQuerySet.__len__()och återanvänder resultatcachen, så inte heller här ställs några databasfrågor.Slingan
for memberitererar över resultatcachen.
Totalt gör denna kod antingen en eller noll databasförfrågningar. Den enda avsiktliga optimering som gjorts är att använda variabeln members. Att använda QuerySet.exists() för if, QuerySet.contains() för in eller QuerySet.count() för count skulle var och en orsaka ytterligare frågor.
Använd QuerySet.update() och delete()¶
I stället för att hämta en mängd objekt, ställa in några värden och spara dem individuellt, använd en bulk SQL UPDATE-sats via QuerySet.update(). Gör på samma sätt bulk deletes när det är möjligt.
Observera dock att dessa metoder för massuppdatering inte kan anropa metoderna save() eller delete() för enskilda instanser, vilket innebär att eventuella anpassade beteenden som du har lagt till för dessa metoder inte kommer att utföras, inklusive allt som drivs från det normala databasobjektet signals.
Använd värden för främmande nycklar direkt¶
Om du bara behöver ett främmande nyckelvärde, använd det främmande nyckelvärde som redan finns på det objekt du har, i stället för att hämta hela det relaterade objektet och ta dess primära nyckel. dvs. gör:
entry.blog_id
istället för:
entry.blog.id
Beställ inte resultat om du inte bryr dig¶
Sortering är inte gratis; varje fält att sortera efter är en åtgärd som databasen måste utföra. Om en modell har en standardsortering (Meta.ordering) och du inte behöver den, ta bort den från en QuerySet genom att anropa order_by() utan parametrar.
Om du lägger till ett index i din databas kan det bidra till att förbättra beställningsresultatet.
Använd bulkmetoder¶
Använd bulkmetoder för att minska antalet SQL-satser.
Skapa i bulk¶
Använd, när det är möjligt, metoden bulk_create() när du skapar objekt för att minska antalet SQL-frågor. Till exempel:
Entry.objects.bulk_create(
[
Entry(headline="This is a test"),
Entry(headline="This is only a test"),
]
)
…är att föredra framför:
Entry.objects.create(headline="This is a test")
Entry.objects.create(headline="This is only a test")
Observera att bulk_create() har flera begränsningar, så säkerställ att metoden passar ditt användningsfall.
Uppdatering i bulk¶
Använd, när det är möjligt, metoden bulk_update() när du uppdaterar objekt för att minska antalet SQL-frågor. Givet en lista eller frågeuppsättning med objekt:
entries = Entry.objects.bulk_create(
[
Entry(headline="This is a test"),
Entry(headline="This is only a test"),
]
)
Följande exempel:
entries[0].headline = "This is not a test"
entries[1].headline = "This is no longer a test"
Entry.objects.bulk_update(entries, ["headline"])
…är att föredra framför:
entries[0].headline = "This is not a test"
entries[0].save()
entries[1].headline = "This is no longer a test"
entries[1].save()
Observera att bulk_update() har flera begränsningar, så säkerställ att metoden passar ditt användningsfall.
Insättning i bulk¶
När du infogar objekt i ManyToManyFields, använd add() med flera objekt för att minska antalet SQL-frågor. Till exempel:
my_band.members.add(me, my_friend)
…är att föredra framför:
my_band.members.add(me)
my_band.members.add(my_friend)
…där Band och Artist är modeller med ett många-till-många-förhållande.
När du infogar olika par av objekt i ManyToManyField eller när den anpassade tabellen through är definierad, använd metoden bulk_create() för att minska antalet SQL-frågor. Till exempel:
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.bulk_create(
[
PizzaToppingRelationship(pizza=my_pizza, topping=pepperoni),
PizzaToppingRelationship(pizza=your_pizza, topping=pepperoni),
PizzaToppingRelationship(pizza=your_pizza, topping=mushroom),
],
ignore_conflicts=True,
)
…är att föredra framför:
my_pizza.toppings.add(pepperoni)
your_pizza.toppings.add(pepperoni, mushroom)
…där Pizza och Topping har en många-till-många-relation. Observera att det finns ett antal avrådan för denna metod, så se till att den är lämplig för ditt användningsfall.
Ta bort i bulk¶
När du tar bort objekt från ManyToManyFields, använd remove() med flera objekt för att minska antalet SQL-frågor. Till exempel:
my_band.members.remove(me, my_friend)
…är att föredra framför:
my_band.members.remove(me)
my_band.members.remove(my_friend)
…där Band och Artist är modeller med ett många-till-många-förhållande.
När du tar bort flera många-till-många-relationer som omfattar flera instanser av de relaterade modellerna, använd metoden delete() på en filtrerad frågeuppsättning för fältets through-modell. Genom att kombinera flera villkor med objekt av typen Q() kan du ta bort flera relationer i en enda fråga. Till exempel:
from django.db.models import Q
PizzaToppingRelationship = Pizza.toppings.through
PizzaToppingRelationship.objects.filter(
Q(pizza=my_pizza, topping=pepperoni)
| Q(pizza=your_pizza, topping=pepperoni)
| Q(pizza=your_pizza, topping=mushroom)
).delete()
…är att föredra framför:
my_pizza.toppings.remove(pepperoni)
your_pizza.toppings.remove(pepperoni, mushroom)
…där Pizza och Topping har ett många-till-många-förhållande.