sql >> Databasteknik >  >> RDS >> Database

Gräver djupare i Django-migrationer

Det här är den andra artikeln i vår Django-migreringsserie:

  • Del 1:Django Migrations:A Primer
  • Del 2:Gräva djupare in i Django-migrering (nuvarande artikel)
  • Del 3:Datamigreringar
  • Video:Django 1.7 Migrations - A Primer

I den tidigare artikeln i den här serien lärde du dig om syftet med Django-migrering. Du har blivit bekant med grundläggande användningsmönster som att skapa och tillämpa migrering. Nu är det dags att gräva djupare i migrationssystemet och ta en titt på några av dess underliggande mekanik.

I slutet av den här artikeln vet du:

  • Hur Django håller reda på migrering
  • Hur migrering vet vilka databasoperationer som ska utföras
  • Hur definieras beroenden mellan migrering

När du har lindat huvudet runt den här delen av Django-migreringssystemet är du väl förberedd för att skapa dina egna anpassade migreringar. Låt oss hoppa in där vi slutade!

Den här artikeln använder bitcoin_tracker Django-projekt byggt i Django Migrations:A Primer. Du kan antingen återskapa det projektet genom att gå igenom artikeln eller så kan du ladda ner källkoden:

Ladda ned källkod: Klicka här för att ladda ner koden för Django-migreringsprojektet som du kommer att använda i den här artikeln.


Hur Django vet vilka migreringar som ska tillämpas

Låt oss sammanfatta det allra sista steget i föregående artikel i serien. Du skapade en migrering och använde sedan alla tillgängliga migreringar med python manage.py migrate .Om kommandot kördes framgångsrikt matchar dina databastabeller nu din modells definitioner.

Vad händer om du kör det kommandot igen? Låt oss prova det:

$ python manage.py migrate
Operations to perform:
  Apply all migrations: admin, auth, contenttypes, historical_data, sessions
Running migrations:
  No migrations to apply.

Inget hände! När en migrering väl har tillämpats på en databas kommer Django inte att tillämpa denna migrering på just den databasen igen. För att säkerställa att en migrering endast tillämpas en gång måste man hålla reda på de migrering som har tillämpats.

Django använder en databastabell som heter django_migrations . Django skapar automatiskt den här tabellen i din databas första gången du tillämpar någon migrering. För varje migrering som tillämpas eller förfalskas, infogas en ny rad i tabellen.

Till exempel, så här ser den här tabellen ut i vår bitcoin_tracker projekt:

ID App Namn Tillämpat
1 contenttypes 0001_initial 2019-02-05 20:23:21.461496
2 auth 0001_initial 2019-02-05 20:23:21.489948
3 admin 0001_initial 2019-02-05 20:23:21.508742
4 admin 0002_logentry_remove... 2019-02-05 20:23:21.531390
5 admin 0003_logentry_add_ac... 2019-02-05 20:23:21.564834
6 contenttypes 0002_remove_content_... 2019-02-05 20:23:21.597186
7 auth 0002_alter_permissio... 2019-02-05 20:23:21.608705
8 auth 0003_alter_user_emai... 2019-02-05 20:23:21.628441
9 auth 0004_alter_user_user... 2019-02-05 20:23:21.646824
10 auth 0005_alter_user_last... 2019-02-05 20:23:21.661182
11 auth 0006_require_content... 2019-02-05 20:23:21.663664
12 auth 0007_alter_validator... 2019-02-05 20:23:21.679482
13 auth 0008_alter_user_user... 2019-02-05 20:23:21.699201
14 auth 0009_alter_user_last... 2019-02-05 20:23:21.718652
15 historical_data 0001_initial 2019-02-05 20:23:21.726000
16 sessions 0001_initial 2019-02-05 20:23:21.734611
19 historical_data 0002_switch_to_decimals 2019-02-05 20:30:11.337894

Som du kan se finns det en post för varje tillämpad migrering. Tabellen innehåller inte bara migreringarna från vår historical_data app, men även migreringarna från alla andra installerade appar.

Nästa gång migrering körs kommer Django att hoppa över migreringarna som anges i databastabellen. Detta innebär att även om du manuellt ändrar filen för en migrering som redan har tillämpats, kommer Django att ignorera dessa ändringar, så länge det redan finns en post för den i databasen.

Du kan lura Django att köra om en migrering genom att ta bort motsvarande rad från tabellen, men detta är sällan en bra idé och kan lämna dig med ett trasigt migreringssystem.



Migreringsfilen

Vad händer när du kör python manage.py makemigrations <appname> ? Django letar efter ändringar som gjorts i modellerna i din app <appname> . Om den hittar någon, som en modell som har lagts till, skapar den en migreringsfil i migrations underkatalog. Den här migreringsfilen innehåller en lista över operationer för att få ditt databasschema att synkronisera med din modelldefinition.

Obs! Din app måste vara listad i INSTALLED_APPS inställning, och den måste innehålla en migrations katalog med en __init__.py fil. Annars kommer Django inte att skapa några migreringar för det.

migrations katalogen skapas automatiskt när du skapar en ny app med startapp managementkommando, men det är lätt att glömma när du skapar en app manuellt.

Migreringsfilerna är bara Python, så låt oss ta en titt på den första migreringsfilen i historical_prices app. Du hittar den på historical_prices/migrations/0001_initial.py . Det borde se ut ungefär så här:

from django.db import models, migrations

class Migration(migrations.Migration):
    dependencies = []
    operations = [
        migrations.CreateModel(
            name='PriceHistory',
            fields=[
                ('id', models.AutoField(
                    verbose_name='ID',
                    serialize=False,
                    primary_key=True,
                    auto_created=True)),
                ('date', models.DateTimeField(auto_now_add=True)),
                ('price', models.DecimalField(decimal_places=2, max_digits=5)),
                ('volume', models.PositiveIntegerField()),
                ('total_btc', models.PositiveIntegerField()),
            ],
            options={
            },
            bases=(models.Model,),
        ),
    ]

Som du kan se innehåller den en enda klass som heter Migration som ärver från django.db.migrations.Migration . Det här är klassen som migreringsramverket letar efter och kör när du ber det att tillämpa migrering.

Migration klass innehåller två huvudlistor:

  1. dependencies
  2. operations

Migreringsåtgärder

Låt oss titta på operations lista först. Den här tabellen innehåller de operationer som ska utföras som en del av migreringen. Operationer är underklasser av klassen django.db.migrations.operations.base.Operation . Här är de vanliga operationerna som är inbyggda i Django:

Operation Class Beskrivning
CreateModel Skapar en ny modell och motsvarande databastabell
DeleteModel Tar bort en modell och släpper dess databastabell
RenameModel Byter namn på en modell och byter namn på dess databastabell
AlterModelTable Byter namn på databastabellen för en modell
AlterUniqueTogether Ändrar de unika begränsningarna för en modell
AlterIndexTogether Ändrar indexen för en modell
AlterOrderWithRespectTo Skapar eller tar bort _order kolumn för en modell
AlterModelOptions Ändrar olika modellalternativ utan att påverka databasen
AlterModelManagers Ändrar tillgängliga hanterare under migrering
AddField Lägger till ett fält i en modell och motsvarande kolumn i databasen
RemoveField Tar bort ett fält från en modell och släpper motsvarande kolumn från databasen
AlterField Ändrar ett fälts definition och ändrar dess databaskolumn vid behov
RenameField Byter namn på ett fält och, om nödvändigt, även dess databaskolumn
AddIndex Skapar ett index i databastabellen för modellen
RemoveIndex Tar bort ett index från databastabellen för modellen

Notera hur operationerna är namngivna efter ändringar som gjorts i modelldefinitioner, inte de åtgärder som utförs på databasen. När du tillämpar en migrering är varje operation ansvarig för att generera de nödvändiga SQL-satserna för din specifika databas. Till exempel CreateModel skulle generera en CREATE TABLE SQL-sats.

Utanför lådan har migreringarna stöd för alla standarddatabaser som Django stöder. Så om du håller dig till operationerna som listas här, så kan du göra mer eller mindre vilka ändringar du vill på dina modeller utan att behöva oroa dig för den underliggande SQL. Det är allt gjort för dig.

Obs! I vissa fall kanske Django inte upptäcker dina ändringar korrekt. Om du byter namn på en modell och ändrar flera av dess fält kan Django förväxla detta med en ny modell.

Istället för en RenameModel och flera AlterField operationer kommer den att skapa en DeleteModel och en CreateModel drift. Istället för att byta namn på databastabellen för modellen kommer den att släppa den och skapa en ny tabell med det nya namnet, vilket i praktiken tar bort all din data!

Gör det till en vana att kontrollera de genererade migreringarna och testa dem på en kopia av din databas innan du kör dem på produktionsdata.

Django tillhandahåller ytterligare tre operationsklasser för avancerade användningsfall:

  1. RunSQL låter dig köra anpassad SQL i databasen.
  2. RunPython låter dig köra vilken Python-kod som helst.
  3. SeparateDatabaseAndState är en specialiserad operation för avancerad användning.

Med dessa operationer kan du i princip göra vilka ändringar du vill i din databas. Du hittar dock inte dessa operationer i en migrering som har skapats automatiskt med makemigrations ledningskommando.

Sedan Django 2.0 finns det även ett par PostgreSQL-specifika operationer tillgängliga i django.contrib.postgres.operations som du kan använda för att installera olika PostgreSQL-tillägg:

  • BtreeGinExtension
  • BtreeGistExtension
  • CITextExtension
  • CryptoExtension
  • HStoreExtension
  • TrigramExtension
  • UnaccentExtension

Observera att en migrering som innehåller en av dessa operationer kräver en databasanvändare med superanvändarbehörighet.

Sist men inte minst kan du också skapa dina egna operationsklasser. Om du vill undersöka det, ta en titt på Django-dokumentationen om hur du skapar anpassade migreringsoperationer.



Migreringsberoenden

dependencies listan i en migreringsklass innehåller alla migreringar som måste tillämpas innan denna migrering kan tillämpas.

I 0001_initial.py migrering som du såg ovan, inget måste tillämpas tidigare så det finns inga beroenden. Låt oss ta en titt på den andra migreringen i historical_prices app. I filen 0002_switch_to_decimals.py , dependencies attribut för Migration har en post:

from django.db import migrations, models

class Migration(migrations.Migration):
    dependencies = [
        ('historical_data', '0001_initial'),
    ]
    operations = [
        migrations.AlterField(
            model_name='pricehistory',
            name='volume',
            field=models.DecimalField(decimal_places=3, max_digits=7),
        ),
    ]

Beroendet ovan säger att migration 0001_initial av appen historical_data måste köras först. Det är vettigt eftersom migreringen 0001_initial skapar tabellen som innehåller fältet som migreringen 0002_switch_to_decimals vill ändra.

En migrering kan också vara beroende av en migrering från en annan app, så här:

class Migration(migrations.Migration):
    ...

    dependencies = [
        ('auth', '0009_alter_user_last_name_max_length'),
    ]

Detta är vanligtvis nödvändigt om en modell har en främmande nyckel som pekar på en modell i en annan app.

Alternativt kan du också tvinga fram att en migrering körs före en annan migrering med attributet run_before :

class Migration(migrations.Migration):
    ...

    run_before = [
        ('third_party_app', '0001_initial'),
    ]

Beroenden kan också kombineras så att du kan ha flera beroenden. Den här funktionen ger mycket flexibilitet, eftersom du kan ta emot främmande nycklar som är beroende av modeller från olika appar.

Alternativet att uttryckligen definiera beroenden mellan migreringarna innebär också att numreringen av migreringarna (vanligtvis 0001 , 0002 , 0003 , …) representerar inte strikt den ordning i vilken migreringarna tillämpas. Du kan lägga till vilket beroende du vill och på så sätt styra ordningen utan att behöva numrera om alla migreringarna.



Visa migreringen

Du behöver i allmänhet inte oroa dig för den SQL som migrering genererar. Men om du vill dubbelkolla att den genererade SQL-koden är meningsfull eller bara är nyfiken på hur den ser ut, då har Django dig täckt med sqlmigrate hanteringskommando:

$ python manage.py sqlmigrate historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
CREATE TABLE "historical_data_pricehistory" (
    "id" integer NOT NULL PRIMARY KEY AUTOINCREMENT,
    "date" datetime NOT NULL,
    "price" decimal NOT NULL,
    "volume" integer unsigned NOT NULL
);
COMMIT;

Om du gör det listar du de underliggande SQL-frågorna som kommer att genereras av den angivna migreringen, baserat på databasen i din settings.py fil. När du skickar parametern --backwards , Django genererar SQL för att avaktivera migreringen:

$ python manage.py sqlmigrate --backwards historical_data 0001
BEGIN;
--
-- Create model PriceHistory
--
DROP TABLE "historical_data_pricehistory";
COMMIT;

När du ser utdata från sqlmigrate för en lite mer komplex migrering kanske du inser att du inte behöver skapa all denna SQL för hand!




Hur Django upptäcker ändringar i dina modeller

Du har sett hur en migreringsfil ser ut och hur dess lista över Operation klasser definierar ändringarna som utförs i databasen. Men exakt hur vet Django vilka operationer som ska gå in i en migreringsfil? Du kan förvänta dig att Django jämför dina modeller med ditt databasschema, men så är inte fallet.

När du kör makemigrations , Django gör inte inspektera din databas. Den jämför inte heller din modellfil med en tidigare version. Istället går Django igenom alla migrationer som har tillämpats och bygger en projektstatus över hur modellerna ska se ut. Detta projekttillstånd jämförs sedan med dina nuvarande modelldefinitioner, och en lista över operationer skapas, som, när den tillämpas, skulle uppdatera projekttillståndet med modelldefinitionerna.


Spela schack med Django

Du kan tänka på dina modeller som ett schackbräde, och Django är en schackstormästare som ser dig spela mot dig själv. Men stormästaren tittar inte på alla dina rörelser. Stormästaren tittar bara på tavlan när du ropar makemigrations .

Eftersom det bara finns en begränsad uppsättning möjliga drag (och stormästaren är en stormästare), kan hon komma på de drag som har hänt sedan hon senast tittade på tavlan. Hon tar några anteckningar och låter dig spela tills du ropar makemigrations igen.

När man tittar på tavlan nästa gång kommer stormästaren inte ihåg hur schackbrädet såg ut senast, men hon kan gå igenom sina anteckningar om de tidigare dragen och bygga en mental modell av hur schackbrädet såg ut.

Nu, när du ropar migrate , kommer stormästaren att spela om alla inspelade drag på ett annat schackbräde och anteckna i ett kalkylblad vilka av hennes rekord som redan har tillämpats. Detta andra schackbräde är din databas, och kalkylarket är django_migrations bord.

Denna analogi är ganska passande, eftersom den på ett bra sätt illustrerar vissa beteenden hos Django-migrering:

  • Django-migreringar försöker vara effektiva: Precis som stormästaren antar att du gjort minst antal drag, kommer Django att försöka skapa de mest effektiva migreringarna. Om du lägger till ett fält med namnet A till en modell, döp sedan om den till B , och kör sedan makemigrations , då kommer Django att skapa en ny migrering för att lägga till ett fält med namnet B .

  • Django-migreringar har sina begränsningar: Om du gör många drag innan du låter stormästaren titta på schackbrädet, kanske hon inte kan spåra de exakta rörelserna för varje pjäs. På samma sätt kanske Django inte kommer på rätt migrering om du gör för många ändringar samtidigt.

  • Django-migrering förväntar sig att du spelar enligt reglerna: När du gör något oväntat, som att ta en slumpmässig pjäs från tavlan eller bråka med noterna, kanske stormästaren inte märker det först, men förr eller senare kommer hon att kasta upp händerna och vägra fortsätta. Samma sak händer när du bråkar med django_migrations tabell eller ändra ditt databasschema utanför migrering, till exempel genom att ta bort databastabellen för en modell.



Förstå SeparateDatabaseAndState

Nu när du känner till projekttillståndet som Django bygger, är det dags att titta närmare på operationen SeparateDatabaseAndState . Denna operation kan göra precis vad namnet antyder:den kan separera projekttillståndet (den mentala modellen som Django bygger) från din databas.

SeparateDatabaseAndState instansieras med två listor med operationer:

  1. state_operations innehåller operationer som endast tillämpas på projekttillståndet.
  2. database_operations innehåller operationer som endast tillämpas på databasen.

Den här operationen låter dig göra någon form av förändring av din databas, men det är ditt ansvar att se till att projektstatusen passar databasen efteråt. Exempel på användningsfall för SeparateDatabaseAndState flyttar en modell från en app till en annan eller skapar ett index på en enorm databas utan driftstopp.

SeparateDatabaseAndState är en avancerad operation och du behöver inte arbeta med migrering första dagen och kanske aldrig alls. SeparateDatabaseAndState liknar hjärtkirurgi. Det innebär en hel del risker och är inte något man gör bara för skojs skull, men ibland är det en nödvändig procedur för att hålla patienten vid liv.




Slutsats

Detta avslutar din djupdykning i Django-migrationer. Grattis! Du har täckt en hel del avancerade ämnen och har nu en gedigen förståelse för vad som händer under migrationshuven.

Du lärde dig att:

  • Django håller reda på tillämpade migreringar i Django-migreringstabellen.
  • Django-migreringar består av vanliga Python-filer som innehåller en Migration klass.
  • Django vet vilka ändringar som ska utföras från operations lista i Migration klasser.
  • Django jämför dina modeller med ett projekttillstånd som det bygger från migreringarna.

Med denna kunskap är du nu redo att ta itu med den tredje delen av serien om Django-migreringar, där du lär dig hur du använder datamigreringar för att säkert göra engångsändringar av din data. Håll utkik!

Den här artikeln använde bitcoin_tracker Django-projekt byggt i Django Migrations:A Primer. Du kan antingen återskapa det projektet genom att gå igenom artikeln eller så kan du ladda ner källkoden:

Ladda ned källkod: Klicka här för att ladda ner koden för Django-migreringsprojektet som du kommer att använda i den här artikeln.



  1. Följerkluster – 3 stora användningsfall för synkronisering av SQL- och NoSQL-distributioner

  2. Skapa tabell DDL med Execute Immediate i Oracle Database Del 2

  3. När ska man använda SELECT ... FÖR UPPDATERING?

  4. Mätning av frågeprestanda:Exekveringsplan för frågekostnad kontra tid