Datenbankmigrationen mit mssql-django

In diesem Artikel wird erläutert, wie das Migrationssystem von Django mit SQL Server über das mssql-django Backend funktioniert, und bekannte Randfälle werden dokumentiert.

Erstellen und Anwenden von Migrationen

Djangos Migrationsworkflow funktioniert auf die gleiche Weise mit SQL Server wie mit anderen Datenbanken:

  1. Generieren Sie Migrationen aus Änderungen am Modell:

    python manage.py makemigrations myapp
    
  2. Überprüfen Sie die generierten Migrationsdateien in <app>/migrations/.

  3. Migrationen auf die Datenbank anwenden:

    python manage.py migrate myapp
    
  4. Migrationsstatus überprüfen:

    python manage.py showmigrations myapp
    

Anfängliches Projektsetup

Wenn Sie ein neues Django-Projekt mit SQL Server einrichten, führen Sie Migrationen aus, um integrierte Django-Tabellen zu erstellen (Authentifizierung, Sitzungen, Administrator):

python manage.py migrate

Mit diesem Befehl werden alle Tabellen erstellt, die für die in INSTALLED_APPS aufgeführten Apps erforderlich sind.

Benutzerdefiniertes SQL in Migrationen

Verwenden Sie migrations.RunSQL, um während der Migrationen rohe SQL-Anweisungen auszuführen. Dieser Ansatz eignet sich zum Erstellen gespeicherter Prozeduren, Trigger oder anderer SQL Server spezifischer Objekte:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0001_initial"),
    ]

    operations = [
        migrations.RunSQL(
            sql="CREATE INDEX IX_myapp_product_name ON myapp_product (name);",
            reverse_sql="DROP INDEX IX_myapp_product_name ON myapp_product;",
        ),
    ]

Bekannte Migrationsgrenzfälle

Die folgenden Migrationsvorgänge erfordern Workarounds, wenn SQL Server als Ziel verwendet wird.

AutoField-Änderung

Das Ändern eines Modellfelds von oder zu AutoField während der Migration wird nicht unterstützt. SQL Server lässt das Hinzufügen oder Entfernen der IDENTITY Eigenschaft aus einer vorhandenen Spalte nicht zu.

Problemumgehung: Erstellen Eines neuen Modells mit dem gewünschten Feldtyp. Migrieren Sie Daten aus der alten Tabelle zur neuen Tabelle, und legen Sie dann die alte Tabelle ab.

Umbenennen von Feld oder Modell mit Fremdschlüsseleinschränkungen

Das Umbenennen eines Felds oder Modells mit Fremdschlüsseleinschränkungen kann fehlschlagen. SQL Server erfordert das Löschen und Neuerstellen von FK-Constraints bei Umbenennungen.

Problemumgehung: Verwenden Sie migrations.SeparateDatabaseAndState, um den FK-Constraint zu löschen, die Spalte umzubenennen und den Constraint neu zu erstellen, und weisen Sie Django dabei an, seinen Modellstatus zu aktualisieren. Im folgenden Beispiel wird der product Fremdschlüssel in einem Order Modell umbenannt in item:

from django.db import migrations

class Migration(migrations.Migration):

    dependencies = [
        ("myapp", "0002_previous"),
    ]

    operations = [
        migrations.SeparateDatabaseAndState(
            database_operations=[
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_product;",
                    reverse_sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_product FOREIGN KEY (product_id) REFERENCES myapp_product(id);",
                ),
                migrations.RunSQL(
                    sql="EXECUTE sp_rename 'myapp_order.product_id', 'item_id', 'COLUMN';",
                    reverse_sql="EXECUTE sp_rename 'myapp_order.item_id', 'product_id', 'COLUMN';",
                ),
                migrations.RunSQL(
                    sql="ALTER TABLE myapp_order ADD CONSTRAINT FK_order_item FOREIGN KEY (item_id) REFERENCES myapp_product(id);",
                    reverse_sql="ALTER TABLE myapp_order DROP CONSTRAINT FK_order_item;",
                ),
            ],
            state_operations=[
                migrations.RenameField(
                    model_name="order",
                    old_name="product",
                    new_name="item",
                ),
            ],
        ),
    ]

Suchen Sie den tatsächlichen Einschränkungsnamen in Ihrer Datenbank, bevor Sie diesen T-SQL-Code ausführen. Django generiert Einschränkungsnamen, die einen kurzen Hash enthalten, sodass der Name in Ihrem Schema nicht mit dem hier gezeigten Platzhalter übereinstimmt.

Migrationen zusammenfassen

Nachdem sich viele Migrationen angesammelt haben, können Sie sie in einer kleineren Anzahl von Dateien zusammenfassen:

python manage.py squashmigrations myapp 0001 0010

Tip

Testen Sie zusammengeführte Migrationen immer mit einer neuen Datenbank, um sicherzustellen, dass sie das korrekte Schema erzeugen.

Generierte Spalten (berechnete Spalten)

Das mssql-django Backend unterstützt Djangos GeneratedField (Django 5.0 und höher), die SQL Server-berechneten Spalten entsprechen.

Gespeicherte (PERSISTED) generierte Spalten

Eine gespeicherte generierte Spalte wird physisch auf den Datenträger geschrieben und aktualisiert, wenn sich die Quellspalten ändern:

from django.db import models
from django.db.models import F

class Product(models.Model):
    price = models.DecimalField(max_digits=10, decimal_places=2)
    tax_rate = models.DecimalField(max_digits=5, decimal_places=4)
    total_price = models.GeneratedField(
        expression=F("price") * (1 + F("tax_rate")),
        output_field=models.DecimalField(max_digits=10, decimal_places=2),
        db_persist=True,
    )

Dies generiert: total_price AS ([price] * (1 + [tax_rate])) PERSISTED.

Virtuelle generierte Spalten

Eine virtuelle generierte Spalte wird zur Abfragezeit berechnet und verbraucht keinen Speicher:

from django.db import models
from django.db.models import F, Value
from django.db.models.functions import Concat

class Employee(models.Model):
    first_name = models.CharField(max_length=50)
    last_name = models.CharField(max_length=50)
    full_name = models.GeneratedField(
        expression=Concat(F("first_name"), Value(" "), F("last_name")),
        output_field=models.CharField(max_length=101),
        db_persist=False,
    )

Note

SQL Server schränkt Indizes für nicht persistierte berechnete Spalten ein. Verwenden Sie db_persist=True, wenn Sie die generierte Spalte indizieren müssen.

Tabellen- und Spaltenkommentare

Das mssql-django Back-End unterstützt Djangos db_comment Feature (Django 4.2 und höher). Kommentare werden als MS_Description erweiterte Eigenschaften für das SQL Server-Objekt gespeichert.

Tabellenkommentare

class AuditLog(models.Model):
    action = models.CharField(max_length=50)
    timestamp = models.DateTimeField(auto_now_add=True)

    class Meta:
        db_table_comment = "Tracks user actions for compliance auditing."

Spaltenkommentare

class Measurement(models.Model):
    value = models.FloatField(db_comment="Sensor reading in Celsius")
    recorded_at = models.DateTimeField(db_comment="UTC timestamp from the data logger")

Kommentare sind in SQL Server Management Studio unter den Spalten- bzw. Tabelleneigenschaften und über sys.extended_properties sichtbar.

Zusammengesetzte Primärschlüssel

Django 5.2 führte CompositePrimaryKey ein. Das mssql-django Back-End unterstützt teilweise zusammengesetzte Primärschlüssel, aber einige Django-Testfälle sind weiterhin ausgeschlossen. Validieren Sie Migrationen mit zusammengesetzten Schlüsseln und Abfragen in Ihrer Anwendung, bevor Sie sie in der Produktion einsetzen.

  • inspectdb generiert zusammengesetzte Primärschlüssel nicht korrekt. Definieren Sie sie manuell nach der Inspektion.
  • Tupelabfragen werden nicht unterstützt. Das Back-End zerlegt zusammengesetzte Schlüsselvergleiche in einzelne Spaltenbedingungen.
  • Tupelvergleiche mit Unterabfragen erfordern Django 5.2.4 und neuere Versionen.
  • Einige Migrationsvorgänge weisen noch bekannte Ausschlüsse auf. Siehe Einschränkungen und nicht unterstützte Features in mssql-django für den aktuellen Status.
from django.db import models
from django.db.models import CompositePrimaryKey

class OrderItem(models.Model):
    pk = CompositePrimaryKey("order_id", "product_id")
    order = models.ForeignKey("Order", on_delete=models.CASCADE)
    product = models.ForeignKey("Product", on_delete=models.CASCADE)
    quantity = models.IntegerField()

IDENTITY_INSERT Handhabung

Wenn Sie explizite Werte in eine AutoField (z. B. das Wiederherstellen von Daten aus einer Sicherung mit bestimmten IDs) einfügen, schließt das Back-End das Einfügen automatisch umSET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF. Es ist kein manuelles SQL erforderlich.

# The backend handles IDENTITY_INSERT automatically
Product.objects.create(id=42, name="Restored Widget", price=9.99)

Note

SQL Server lässt zu, dass jeweils nur eine Tabelle pro Sitzung gleichzeitig über IDENTITY_INSERT ON verfügt. Wenn Sie in einem einzigen atomic()-Block explizite IDs in mehrere Tabellen einfügen, behandelt das Backend das Umschalten für jede Anweisung einzeln. Es kann jedoch zu Konflikten mit gleichzeitigen Sessions kommen, die ebenfalls IDENTITY_INSERT in derselben Tabelle verwenden.