Django-alkalmazások migrálása a PostgreSQL-ből a SQL Server

Ez a cikk részletes migrálási útmutató a PostgreSQL-ről (psycopg2vagy psycopg) a SQL Server (mssql-django) közötti Django-alkalmazásokra. A bármely adatbázisból történő migrálás általános áttekintéséért lásd: Django-alkalmazások migrálása más adatbázisokból az SQL Serverbe.

Prerequisites

  • Python 3.8 vagy újabb
  • Microsoft ODBC Driver 17 vagy 18 SQL Serverhez. Lásd: Az mssql-django telepítése.
  • 2016-os vagy újabb SQL Server vagy Azure SQL Database

Az adatbázis háttérrendszerének váltása

Cserélje le a PostgreSQL-konfigurációt a következő helyen settings.py:

# Before (PostgreSQL)
DATABASES = {
    "default": {
        "ENGINE": "django.db.backends.postgresql",
        "NAME": "mydb",
        "USER": "myuser",
        "PASSWORD": "mypassword",
        "HOST": "localhost",
        "PORT": "5432",
    },
}

# After (SQL Server)
DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "mydb",
        "USER": "myuser",
        "PASSWORD": "mypassword",
        "HOST": "localhost",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Frissítés requirements.txt:

# Remove
# psycopg2-binary>=2.9
# or psycopg[binary]>=3.1

# Add
mssql-django>=1.5

Django.contrib.postgres funkciók cseréje

A django.contrib.postgres modul PostgreSQL-specifikus mezőket, függvényeket és kereséseket biztosít. Ezek nem működnek az SQL Serverrel. Az alábbi szakaszok bemutatják, hogyan cserélheti le az egyes funkciókat.

Tömbmező

A PostgreSQL ArrayField natív módon tárolja a tömböket. SQL Server nem rendelkezik tömboszloptípussal.

1. lehetőség: JSONField (a Django 3.2 és újabb verzióival működik)

# Before
from django.contrib.postgres.fields import ArrayField

class Product(models.Model):
    tags = ArrayField(models.CharField(max_length=50), default=list)

# After
class Product(models.Model):
    tags = models.JSONField(default=list)

Változások lekérdezése:

# Before (PostgreSQL)
Product.objects.filter(tags__contains=["sale"])
Product.objects.filter(tags__overlap=["sale", "new"])
Product.objects.filter(tags__len=3)

# After (SQL Server with JSONField)
# Use __contains for exact list matching
Product.objects.filter(tags__contains=["sale"])

# For overlap-style queries, use raw SQL
from django.db.models.expressions import RawSQL
Product.objects.filter(
    pk__in=RawSQL(
        """
        SELECT p.id FROM products_product p
        CROSS APPLY OPENJSON(p.tags) t
        WHERE t.value IN (%s, %s)
        """,
        ["sale", "new"],
    )
)

2. lehetőség: Kapcsolódó táblázat (normalizált, nagyobb tömbök vagy gyakori szűrés esetén jobb)

class Product(models.Model):
    name = models.CharField(max_length=200)

class ProductTag(models.Model):
    product = models.ForeignKey(Product, on_delete=models.CASCADE, related_name="tags")
    tag = models.CharField(max_length=50, db_index=True)

    class Meta:
        unique_together = [("product", "tag")]

HStoreField

Cserélje le a következőre JSONField:

# Before
from django.contrib.postgres.fields import HStoreField

class Profile(models.Model):
    metadata = HStoreField(default=dict)

# After
class Profile(models.Model):
    metadata = models.JSONField(default=dict)

JSONField ugyanazt a kulcskeresési szintaxist támogatja:

# Both backends support this
Profile.objects.filter(metadata__theme="dark")

Tartománymezők

A PostgreSQL-tartománytípusok (IntegerRangeField, BigIntegerRangeField, DateRangeField, DateTimeRangeField, ) DecimalRangeFieldnem rendelkeznek SQL Server egyenértékű típusokkal. Két külön mező használata:

# Before
from django.contrib.postgres.fields import DateRangeField

class Event(models.Model):
    dates = DateRangeField()

# After
class Event(models.Model):
    start_date = models.DateField()
    end_date = models.DateField()

Frissítse a lekérdezéseket, hogy külön mező-összehasonlításokat használjanak. Korábban, a PostgreSQL DateRangeField esetén:

from django.contrib.postgres.fields import DateRangeField
from psycopg2.extras import DateRange

Event.objects.filter(dates__contains=DateRange(start, end))

Ezután, két DateField oszloppal az SQL Serverben:

from datetime import date

start = date(2026, 1, 1)
end = date(2026, 12, 31)

Event.objects.filter(start_date__lte=start, end_date__gte=end)

CITextField és CIEmailField

A PostgreSQL kis- és nagybetűkre érzékeny szövegtípusai a bővítményt citext használják. A SQL Server alapértelmezett rendezése (SQL_Latin1_General_CP1_CI_AS) már eleve nem különbözteti meg a kis- és nagybetűket, ezért a szabványos CharField és EmailField ugyanúgy viselkednek:

# Before
from django.contrib.postgres.fields import CITextField

class Tag(models.Model):
    name = CITextField(max_length=100)

# After - already case-insensitive with default SQL Server collation
class Tag(models.Model):
    name = models.CharField(max_length=100)

SearchVector, SearchQuery, SearchRank

A PostgreSQL teljes szöveges keresés mélyen integrálva van a Django-val. SQL Server saját teljes szöveges keresőmotorral rendelkezik, de nincs Django ORM-integráció. A teljes szöveges keresés migrálását a cikk későbbi részében tekintheti meg.

Összesítő függvények

Cserélje le a PostgreSQL-specifikus aggregátumokat:

# Before
from django.contrib.postgres.aggregates import ArrayAgg, StringAgg

Product.objects.values("category").annotate(
    all_names=ArrayAgg("name"),
    name_list=StringAgg("name", delimiter=", "),
)

# After - use SQL Server equivalents via RawSQL
from django.db.models.expressions import RawSQL

Product.objects.values("category").annotate(
    name_list=RawSQL(
        "STRING_AGG(name, ', ') WITHIN GROUP (ORDER BY name)",
        [],
    ),
)

Note

STRING_AGG használatához SQL Server 2017 vagy újabb, illetve Azure SQL Database szükséges.

Teljes szöveges keresés áttelepítése

A PostgreSQL teljes szöveges keresése tsvector, tsquery és GIN indexeket használ. SQL Server külön teljes szöveges keresőmotorral rendelkezik.

Teljes szöveges keresés engedélyezése SQL Server

-- Create a full-text catalog
CREATE FULLTEXT CATALOG [MyAppCatalog] AS DEFAULT;

-- Create a full-text index (table must have a unique index)
CREATE FULLTEXT INDEX ON [products_product]([name], [description])
KEY INDEX [PK_products_product]
WITH CHANGE_TRACKING AUTO;

Teljes szöveges keresés lekérdezése Django-ból

A(z) SQL Server CONTAINS és FREETEXT függvényeinek elérése nyers SQL használatával:

from django.db.models.expressions import RawSQL

# Equivalent of PostgreSQL SearchVector + SearchQuery
def search_products(query):
    return Product.objects.filter(
        pk__in=RawSQL(
            """
            SELECT p.id FROM products_product p
            WHERE CONTAINS((p.name, p.description), %s)
            """,
            [query],
        )
    )

Rangsorolt eredmények esetén (egyenértékű a következő értékekkel SearchRank):

def search_products_ranked(query):
    return Product.objects.raw(
        """
        SELECT p.*, ft.[RANK]
        FROM products_product p
        INNER JOIN CONTAINSTABLE(products_product, (name, description), %s) ft
            ON p.id = ft.[KEY]
        ORDER BY ft.[RANK] DESC
        """,
        [query],
    )

Teljes szövegű index karbantartási eljárásrend

Tervezze meg SQL Server teljes szöveges indexek karbantartását a migrálás után:

  • Közel valós idejű frissítésekhez használható CHANGE_TRACKING AUTO .
  • Használja a CHANGE_TRACKING MANUAL elemet a tömeges betöltési ablakokhoz, majd futtasson egy teljes feltöltést.
  • Kövesse nyomon a bejárás állapotát és a hátralékot a következőn keresztül: sys.fulltext_indexes és sys.dm_fts_index_population.

Állapot ellenőrzése:

SELECT
    OBJECT_NAME(i.object_id) AS table_name,
    i.change_tracking_state_desc,
    i.has_crawl_completed,
    i.crawl_type_desc
FROM sys.fulltext_indexes AS i;

Nagy adatbetöltés után manuális nyomkövetéssel:

ALTER FULLTEXT INDEX ON [products_product] START FULL POPULATION;

Tip

Teljes szöveges indexek újraépítése vagy újratöltése alacsony forgalmú ablakok esetén. A teljes populációk költségesek lehetnek nagy táblákon.

Kereséskezelő létrehozása

Csomagolja be a nyers SQL-t egy kezelőbe a tiszta hozzáférés érdekében:

class ProductSearchManager(models.Manager):
    def search(self, query):
        if not query:
            return self.none()
        return self.filter(
            pk__in=RawSQL(
                """
                SELECT p.id FROM products_product p
                WHERE CONTAINS((p.name, p.description), %s)
                """,
                [query],
            )
        )

class Product(models.Model):
    name = models.CharField(max_length=200)
    description = models.TextField()

    objects = ProductSearchManager()
    # Usage: Product.objects.search("mountain bike")

PostGIS és térbeli adatok

mssql-django nem tartalmaz GeoDjango GIS-háttérrendszert. Ha a PostgreSQL-alkalmazása a PostGIS-t a django.contrib.gis használatával éri el, a térbeli lekérdezéseket nem lehet közvetlenül áttelepíteni az SQL Serveren használt Django ORM-be.

SQL Server támogatja a földrajzi és geometriai adattípusokat natív módon. Térbeli adatok használata a migrálás után:

  • A térbeli adatokat nyers SQL- vagy egyéni modellmezők használatával tárolhatja, amelyek SQL Server földrajzi vagy geometriai oszlopaihoz lesznek megfeleltetve.
  • Térbeli adatok lekérdezése nyers SQL használatával SQL Server beépített térbeli függvényeivel:
from django.db import connection

with connection.cursor() as cursor:
    cursor.execute(
        """
        SELECT id, name
        FROM stores
        WHERE location.STDistance(geography::Point(%s, %s, 4326)) <= %s
        """,
        [latitude, longitude, radius_meters],
    )
  • Fontolja meg azokat a külső kódtárakat, amelyek SQL Server térbeli támogatást adnak a Django-hoz, vagy a térbeli lekérdezéseket nyers SQL-ként tartják, miközben az ORM-et minden máshoz használják.

Note

Ha az alkalmazás nagymértékben függ a GeoDjango térbeli keresésétől, gondosan értékelje ki a migrálás költségét. A térbeli lekérdezések nyers SQL-re való áthelyezéséhez minden GeoDjango térbeli szűrő újraírása szükséges.

A kapcsolatcsoport migrálása

Ha a PostgreSQL-alkalmazása a pgbouncer elemet kapcsolatkészletezéshez használja, cserélje le a Django beépített kapcsolatkezelésére vagy az ODBC kapcsolatkészletezésre.

Django-kapcsolat újrafelhasználása

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "HOST": "<your-server>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
        "CONN_MAX_AGE": 600,  # Reuse connections for 10 minutes
        "CONN_HEALTH_CHECKS": True,  # Django 4.1+
    },
}

További részletekért lásd az mssql-django kapcsolatkészletezését.

DISTINCT ON helyettesítése

A PostgreSQL támogatja DISTINCT ON a csoportonkénti egy sor lekérését. SQL Server nem támogatja ezt a szintaxist. Használja inkább az ablakfüggvényeket:

# Before (PostgreSQL)
Entry.objects.order_by("blog_id", "-pub_date").distinct("blog_id")

# After (SQL Server) - use raw SQL with ROW_NUMBER
Entry.objects.raw(
    """
    SELECT * FROM (
        SELECT *, ROW_NUMBER() OVER (PARTITION BY blog_id ORDER BY pub_date DESC) AS rn
        FROM blog_entry
    ) sub
    WHERE rn = 1
    """
)

JSONB-lekérdezések

A PostgreSQL típusa támogatja a jsonb gazdag lekérdezési operátorokat. Az SQL Server a JSON-t nvarchar(max) formátumban tárolja, és az SQL Server 2016 óta lekérdezési függvények is elérhetők hozzá.

A Django keresési szintaxisa JSONField az alapműveletek mindkét háttérrendszerén működik:

# Works on both PostgreSQL and SQL Server
Config.objects.filter(data__settings__theme="dark")
Config.objects.filter(data__has_key="settings")

A Django ORM által nem támogatott speciális JSON-lekérdezésekhez használja az SQL Server JSON_VALUE és OPENJSON függvényeit:

from django.db.models.expressions import RawSQL

# Query nested JSON values
Config.objects.annotate(
    theme=RawSQL("JSON_VALUE(data, '$.settings.theme')", [])
).filter(theme="dark")

PostgreSQL-függőségek eltávolítása

A migrálás után távolítsa el a PostgreSQL-csomagokat a projektből:

pip uninstall psycopg2-binary psycopg2 psycopg

Távolítsa el a(z) django.contrib.postgres elemet a(z) INSTALLED_APPS elemből itt: settings.py:

INSTALLED_APPS = [
    # Remove this line:
    # "django.contrib.postgres",
    "django.contrib.admin",
    "django.contrib.auth",
    # ...
]

Migrálási ellenőrzőlista

Lépés Részletek
Háttérrendszer váltása Cserélje le django.db.backends.postgresql a következőremssql: .settings.py
A contrib.postgres cseréje Cserélje fel a ArrayField, HStoreField, tartománymezőket és CI-mezőket.
Teljes szöveges keresés frissítése Migrálás a(z) tsvector/tsquery rendszerről a SQL Server CONTAINS/FREETEXT verzióra.
Térbeli lekérdezések frissítése A GeoDjango-keresések átírása nyers SQL-ként SQL Server térbeli függvények használatával.
Helyettesít DISTINCT ON Használja a ROW_NUMBER() ablakfüggvényeket.
Nyers SQL frissítése Módosítsa a PostgreSQL szintaxisát (LIMIT, ||, NOW()) SQL Server szintaxisra. Lásd: Az egyéni SQL frissítése.
RCSI engedélyezése Állítsa be READ_COMMITTED_SNAPSHOT ON a PostgreSQL MVCC viselkedésének megfelelőre. Lásd a tranzakcióelkülönítési különbségeket.
Tesztrendezés Ellenőrizze, hogy a kis- és nagybetűk érzékenysége megfelel-e az elvárásainak. Lásd: Rendezési különbségek.
A psycopg2 eltávolítása Távolítsa el a psycopg2-binary vagy a psycopg elemet. Távolítsa el django.contrib.postgres.
Migrációk újragenerálása Törölje a régi migrációs fájlokat, majd futtassa újra a makemigrations és a migrate parancsot.
Adatok migrálása Nagy adathalmazokhoz használja a dumpdata/loaddata elemet vagy egy ETL-eszközt.