Migrar aplicações Django de outras bases de dados para o SQL Server

Este artigo fornece orientações para migrar aplicações Django do PostgreSQL, MySQL ou SQLite para o SQL Server usando o mssql-django backend.

Overview

O ORM do Django abstrai a maioria das diferenças nas bases de dados, mas alguns comportamentos e dialetos SQL variam entre backends. Este guia cobre as principais diferenças que encontra ao migrar para o SQL Server.

Passo 1: Instalar mssql-django

Instale o mssql-django pacote e as suas dependências:

pip install mssql-django

Certifique-se de que o driver Microsoft ODBC para SQL Server está instalado. Consulte Instalar mssql-django para instruções específicas da plataforma.

Passo 2: Atualizar DATABASE a configuração

Substitua a configuração existente da sua base de dados em settings.py:

# Example: From PostgreSQL
# DATABASES = {
#     "default": {
#         "ENGINE": "django.db.backends.postgresql",
#         "NAME": "mydb",
#     },
# }

# To SQL Server
DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "USER": "<your-username>",
        "PASSWORD": "<your-password>",
        "HOST": "<your-server>",
        "PORT": "1433",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
        },
    },
}

Passo 3: Criar novas migrações

Comece com um histórico de migração limpo para o SQL Server:

# Remove existing migration files (keep __init__.py)
# Then regenerate
python manage.py makemigrations
python manage.py migrate

Importante

Transfira os seus dados usando uma migração de dados ou um processo ETL separado. Não tentes executar ficheiros de migração PostgreSQL ou MySQL contra o SQL Server.

Principais diferenças em relação ao PostgreSQL

Característica PostgreSQL SQL Server (mssql-django)
Incremento automático SERIAL / BIGSERIAL IDENTITY(1,1)
Tipo booleano Nativo boolean bit (0 ou 1)
Campos de texto text (ilimitado) nvarchar(max)
Suporte a JSON Nativo jsonb nvarchar(max) com funções JSON (SQL Server 2016+)
Campos de arrays ArrayField Não suportado. Use uma tabela relacionada ou JSON.
Campos de HStore HStoreField Não suportado. Utilize JSONField em substituição.
Campos de intervalo IntegerRangeField, BigIntegerRangeField, DateRangeField, DateTimeRangeField Não suportado. Usa dois campos separados.
Pesquisa em texto completo SearchVector, SearchRank Utilize SQL em bruto com a pesquisa de texto completo do SQL Server.
DISTINCT ON Supported Não suportado. Uso GROUP BY ou subconsultas.
DateTimeField com fuso horário timestamp with time zone datetimeoffset (quando USE_TZ=True) ou datetime2

Funcionalidades específicas do PostgreSQL para substituir

Se o seu código utiliza funcionalidades específicas do PostgreSQL de django.contrib.postgres, substitua-as:

# PostgreSQL ArrayField - replace with JSONField or related table
# Before
from django.contrib.postgres.fields import ArrayField
tags = ArrayField(models.CharField(max_length=50))

# After (using JSONField)
tags = models.JSONField(default=list)

# PostgreSQL HStoreField - replace with JSONField
# Before
from django.contrib.postgres.fields import HStoreField
metadata = HStoreField()

# After
metadata = models.JSONField(default=dict)

Principais diferenças em relação ao MySQL

Característica MySQL SQL Server (mssql-django)
Incremento automático AUTO_INCREMENT IDENTITY(1,1)
Tipo booleano tinyint(1) bit
Campos de texto longtext nvarchar(max)
Suporte a JSON Nativo JSON (5.7 e posteriores) nvarchar(max) com funções JSON
Collation Configurável por coluna Nível de instância ou de base de dados (pode ser substituído com a opção COLLATE)
DateTimeField datahora(6) datetimeoffset ou datetime2

Principais diferenças em relação ao SQLite

Característica SQLite SQL Server (mssql-django)
Imposição de tipos Tipagem flexível Imposição estrita de tipos
Gravações simultâneas Limitado Suporte total de concorrência
Número máximo de ligações Basicamente 1 escritor Agrupamento de ligações com muitas ligações concorrentes
DateTimeField Armazenado como texto datetimeoffset ou datetime2

Diferenças de agrupamento

A Collation controla como o SQL Server compara e ordena o texto. Esta é uma das fontes mais comuns de comportamento inesperado ao migrar do PostgreSQL ou do MySQL.

Sensível às maiúsculas e minúsculas

A colação predefinida do SQL Server (SQL_Latin1_General_CP1_CI_AS) é insensível a maiúsculas e minúsculas. O PostgreSQL é sensível a maiúsculas minúsculas por defeito.

Este comportamento significa que, após a migração, as consultas que anteriormente distinguiam entre "Smith" e "smith" passam a tratá-los como iguais:

# On PostgreSQL: returns only exact case matches
# On SQL Server (default collation): returns both "Smith" and "smith"
User.objects.filter(last_name="Smith")

Se a sua aplicação depender de comparações que diferenciam maiúsculas de minúsculas, tem duas opções:

  • Altere o agrupamento da base de dados ou da coluna para uma variante sensível a maiúsculas e minúsculas:

    -- Database-level (affects all new columns)
    ALTER DATABASE [<your-database>] COLLATE Latin1_General_CS_AS;
    
    -- Column-level (for specific columns)
    ALTER TABLE [<your-table>]
    ALTER COLUMN [<column-name>] NVARCHAR (150) COLLATE Latin1_General_CS_AS;
    
  • Use a operação de pesquisa do Django com uma sobrescrita de ordenação em SQL nativo para consultas específicas.

Sensibilidade ao sotaque

A colação padrão do SQL Server é sensível ao acento (AS), o que corresponde ao comportamento do PostgreSQL. Caracteres como é e e são tratados como diferentes. Se precisares de comparações insensíveis ao acento, usa uma colação que termina em _AI.

Configurar a colação no mssql-django

Substitui a ordenação predefinida das pesquisas em campos de texto na configuração da base de dados:

DATABASES = {
    "default": {
        "ENGINE": "mssql",
        "NAME": "<your-database>",
        "OPTIONS": {
            "driver": "ODBC Driver 18 for SQL Server",
            "collation": "Latin1_General_CS_AS",  # Case-sensitive
        },
    },
}

Note

A opção collation em mssql-django controla a ordenação usada em LIKE e nas operações de comparação geradas pelas pesquisas do ORM do Django. Não altera a ordenação das colunas existentes na base de dados. Para alterar a ordenação armazenada da coluna, use instruções ALTER TABLE / ALTER COLUMN. Para mais informações, consulte a documentação de compilação do SQL Server.

Passo 4: Atualizar SQL personalizado

Se o seu código contém SQL bruto, atualize-o para a sintaxe do SQL Server:

# PostgreSQL syntax
# cursor.execute("SELECT * FROM products LIMIT 10 OFFSET 20")

# SQL Server syntax
cursor.execute("SELECT * FROM products ORDER BY id OFFSET 20 ROWS FETCH NEXT 10 ROWS ONLY")

Diferenças comuns na sintaxe SQL:

Operação PostgreSQL/MySQL SQL Server
Limitar resultados LIMIT 10 TOP 10 ou OFFSET ... FETCH NEXT ...
Concatenação de cordas \|\| (PG) / CONCAT() + ou CONCAT()
Literais booleanos TRUE / FALSE 1 / 0
Carimbo de data/hora atual NOW() GETDATE(), SYSDATETIME(), ou SYSDATETIMEOFFSET() para valores com reconhecimento de fuso horário
SE NÃO EXISTE CREATE TABLE IF NOT EXISTS Verificar sys.objects ou usar IF NOT EXISTS

Diferenças de isolamento de transações

O PostgreSQL utiliza MVCC (Multi-Version Concurrency Control) para o seu READ COMMITTED nível de isolamento. Os leitores nunca bloqueiam escritores e escritores nunca bloqueiam leitores.

A predefinição READ COMMITTED do SQL Server utiliza bloqueios, o que significa que as consultas de leitura podem ficar bloqueadas enquanto esperam que as transações de escrita sejam concluídas. Se a sua aplicação sofrer um aumento do bloqueio após a migração, considere ativar READ COMMITTED SNAPSHOT a base de dados:

ALTER DATABASE [<your-database>]
SET READ_COMMITTED_SNAPSHOT ON;

Isto faz com que o READ COMMITTED do SQL Server passe a utilizar o controlo de versões de linhas (semelhante ao MVCC do PostgreSQL) em vez de bloqueios. Os leitores veem a última versão comprometida de uma linha sem esperar por escritores ativos.

Note

READ COMMITTED SNAPSHOT requer espaço adicional tempdb para versões de linhas. Teste sob uma carga realista antes de ativar em ambiente de produção. Para mais informações, consulte Gestão de transações em mssql-django.

Passo 5: Migrar dados

A estratégia de migração de dados depende do tamanho do conjunto de dados:

Conjuntos de dados pequenos (<500 MB)

Utilize os dumpdata/loaddata do Django:

# On the source database
python manage.py dumpdata --natural-foreign --natural-primary -o data.json

# Switch settings.py to SQL Server, then:
python manage.py migrate
python manage.py loaddata data.json

Grandes conjuntos de dados (>500 MB)

Para migrações grandes, utilize ferramentas especializadas para evitar esgotamento de memória e problemas de timeout. O ORM da Django não é a ferramenta certa para cargas em massa a esta escala. Ignora isso para a transferência de dados e deixa o Django gerir o esquema e a lógica da aplicação depois.

Tool Melhor para
Assistente de Importação e Exportação do SQL Server Migrações on-premises para on-premises com interface gráfica
Fábrica de Dados do Azure Qualquer fonte para SQL do Azure, incluindo cenários híbridos
Serviço de Migração de Banco de Dados do Azure Migrações em grande escala com validação e rollback incorporados
mssql-python: cópia em massa com Apache Arrow Pipelines Python personalizados que precisam de máximo rendimento entre SQL Server, Base de Dados SQL do Azure e SQL Database em Fabric

Validação pós-migração

Após a migração, valide a consistência da semente de identidade para colunas de incremento automático:

-- Check identity seed and current value for all tables
SELECT 
    TABLE_NAME,
    IDENT_SEED(TABLE_SCHEMA + '.' + TABLE_NAME) AS IdentitySeed,
    IDENT_INCR(TABLE_SCHEMA + '.' + TABLE_NAME) AS IdentityIncrement,
    IDENT_CURRENT(TABLE_SCHEMA + '.' + TABLE_NAME) AS CurrentIdentity
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
    AND OBJECTPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME), 'TableHasIdentity') = 1
ORDER BY TABLE_NAME;

Se CurrentIdentity for superior a IdentitySeed + record_count, reinicialize a propagação:

DBCC CHECKIDENT ('your_table', RESEED, new_seed);