Перенос приложений Django из других баз данных в SQL Server

В этой статье приведены рекомендации по миграции приложений Django с PostgreSQL, MySQL или SQLite на SQL Server с помощью серверного модуля mssql-django.

Overview

ORM Django абстрагирует большинство различий между СУБД, но некоторые аспекты поведения и диалекты SQL различаются в зависимости от используемой СУБД. В этом руководстве рассматриваются основные различия, которые возникают при миграции на SQL Server.

Шаг 1. Установка mssql-django

Установите пакет mssql-django и его зависимости:

pip install mssql-django

Убедитесь, что установлен драйвер ODBC Microsoft для SQL Server. Инструкции для конкретной платформы см. в разделе "Установка mssql-django ".

Шаг 2. Обновление DATABASE конфигурации

Замените существующую конфигурацию базы данных в 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",
        },
    },
}

Шаг 3. Создание новых миграций

Начните с чистой истории миграции для SQL Server:

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

Important

Перенесите свои данные с помощью процесса миграции данных или отдельного ETL-процесса. Не пытайтесь запускать файлы миграции для PostgreSQL или MySQL на SQL Server.

Основные отличия от PostgreSQL

Feature PostgreSQL SQL Server (mssql-django)
Автоматическое увеличение SERIAL / BIGSERIAL IDENTITY(1,1)
Тип Boolean Родной boolean bit (0 или 1)
Текстовые поля text (неограниченно) nvarchar(max)
Поддержка JSON Нативный jsonb nvarchar(max) с функциями JSON (SQL Server 2016+)
Поля массива ArrayField Не поддерживается. Используйте связанную таблицу или JSON.
Поля HStore HStoreField Не поддерживается. Вместо этого используйте JSONField.
Поля диапазона IntegerRangeField, BigIntegerRangeField, DateRangeField, DateTimeRangeField Не поддерживается. Используйте два отдельных поля.
Полнотекстовый поиск SearchVector, SearchRank Используйте необработанные SQL-запросы для полнотекстового поиска SQL Server.
DISTINCT ON Поддерживается Не поддерживается. Используйте GROUP BY или подзапросы.
DateTimeField с часовым поясом timestamp with time zone datetimeoffset (когда USE_TZ=True) или datetime2

Специфичные для PostgreSQL возможности для замены

Если в коде используются специальные функции django.contrib.postgresPostgreSQL, замените их:

# 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)

Основные отличия от MySQL

Feature MySQL SQL Server (mssql-django)
Автоматическое увеличение AUTO_INCREMENT IDENTITY(1,1)
Тип Boolean tinyint(1) bit
Текстовые поля longtext nvarchar(max)
Поддержка JSON Нативная JSON (5.7 и выше) nvarchar(max) с функциями JSON
Collation Настройка для каждого столбца Уровень экземпляра или базы данных (переопределяется параметром COLLATE)
DateTimeField datetime(6) datetimeoffset или datetime2

Основные отличия от SQLite

Feature SQLite SQL Server (mssql-django)
Проверка типов Гибкая типизация Строгая типизация
Одновременные операции записи Limited Полная поддержка параллелизма
Максимальное количество соединений Фактически 1 автор Пул подключений с большим количеством одновременных подключений
DateTimeField Хранящийся в виде текста datetimeoffset или datetime2

Различия в параметрах сортировки

Сортировка определяет, как SQL Server сравнивает и сортирует текст. Это один из наиболее распространенных источников неожиданного поведения при миграции из PostgreSQL или MySQL.

Чувствительность к регистру

Параметры сортировки SQL Server по умолчанию (SQL_Latin1_General_CP1_CI_AS) нечувствительны к регистру. PostgreSQL по умолчанию учитывает регистр.

Это означает, что после миграции запросы, которые ранее различали "Smith" и "smith", считают их равными:

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

Если приложение зависит от сравнения с учетом регистра, у вас есть два варианта:

  • Измените параметры сортировки базы данных или столбца на вариант с учетом регистра:

    -- 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;
    
  • Используйте в чистом SQL оператор поиска Django __exact с переопределением правил сортировки для точечных запросов.

Чувствительность к диакритическим знакам

Правила сортировки SQL Server по умолчанию чувствительны к диакритическим знакам (AS), что соответствует поведению PostgreSQL. Символы, как é и e рассматриваются как разные. Если вам нужны сравнения, нечувствительные к акцентам, используйте правило сортировки, оканчивающееся на _AI.

Настройка сортировки в mssql-django

Переопределите сортировку по умолчанию для операций поиска по текстовым полям в конфигурации базы данных:

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

Note

Параметр collation в mssql-django определяет правила сортировки, используемые в LIKE и операциях сравнения, генерируемых lookup-выражениями ORM Django. Он не изменяет параметры сортировки существующих столбцов в базе данных. Чтобы изменить правила сортировки для хранимых столбцов, используйте операторы ALTER TABLE / ALTER COLUMN. Дополнительные сведения см. в документации по сортировке SQL Server.

Шаг 4. Обновление пользовательского SQL

Если код содержит необработанный SQL, обновите его для синтаксиса 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")

Распространенные различия синтаксиса SQL:

Операция PostgreSQL/MySQL SQL Server
Ограничение результатов LIMIT 10 TOP 10 или OFFSET ... FETCH NEXT ...
Объединение строк \|\| (PG) / CONCAT() + или CONCAT()
Логические литералы TRUE / FALSE 1 / 0
Текущая метка времени NOW() GETDATE(), SYSDATETIME()или SYSDATETIMEOFFSET() для значений с учетом часового пояса
ЕСЛИ НЕ СУЩЕСТВУЕТ CREATE TABLE IF NOT EXISTS Проверьте sys.objects или используйте IF NOT EXISTS

Различия изоляции транзакций

PostgreSQL использует MVCC (многоверсионное управление параллелизмом) для своего уровня изоляции READ COMMITTED. Читатели никогда не блокируют писателей и писателей никогда не блокируют читателей.

по умолчанию READ COMMITTED SQL Server использует блокировку, что означает, что запросы чтения могут блокироваться при ожидании завершения транзакций записи. Если после миграции в приложении наблюдается усиление блокировок, рассмотрите возможность включения READ COMMITTED SNAPSHOT в базе данных:

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

Это приводит к тому, что SQL Server READ COMMITTED использует версионирование строк (аналогично MVCC в PostgreSQL) вместо блокировок. Читатели видят последнюю зафиксированную версию строки, не дожидаясь завершения активных процессов записи.

Note

READ COMMITTED SNAPSHOT требует дополнительного tempdb пространства для версий строк. Проведите тестирование под реалистичной нагрузкой перед включением в производственной среде. Дополнительные сведения см. в разделе "Управление транзакциями" в mssql-django.

Шаг 5. Перенос данных

Стратегия миграции данных зависит от размера набора данных:

Небольшие наборы данных (<500 МБ)

Используйте Django dumpdata/loaddata:

# 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

Большие наборы данных (>500 МБ)

Для больших миграций используйте специализированные средства, чтобы избежать проблем с нехваткой памяти и временем ожидания. ORM в Django — не подходящий инструмент для массовой загрузки данных при таком масштабе. Не используйте его при переносе данных, а затем позвольте Django управлять схемой и логикой приложения.

Tool лучше всего подходит для
мастер импорта и экспорта SQL Server Миграции из локальной среды в локальную среду с графическим интерфейсом
Фабрика данных Azure Любой источник Azure SQL, включая гибридные сценарии
Azure Database Migration Service Крупномасштабные миграции со встроенной проверкой и откатом
массовая копия mssql-python с помощью Apache Arrow Пользовательские конвейеры Python, требующие максимальной пропускной способности между SQL Server, База данных SQL Azure и базой данных SQL в Fabric

Проверка после миграции

После миграции проверьте согласованность начального значения identity для столбцов с автоинкрементом:

-- 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;

Если CurrentIdentity превышает IdentitySeed + record_count, выполните повторную инициализацию:

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