mssql-django를 사용하여 데이터베이스 마이그레이션

이 글에서는 Django의 마이그레이션 시스템이 백엔드를 통해 mssql-django SQL Server와 어떻게 작동하는지 설명하고, 예외 사례를 문서화합니다.

마이그레이션 만들기 및 적용

Django의 마이그레이션 워크플로는 다른 데이터베이스와 마찬가지로 SQL Server 동일한 방식으로 작동합니다.

  1. 모델 변경 내용에서 마이그레이션 생성:

    python manage.py makemigrations myapp
    
  2. <app>/migrations/에서 생성된 마이그레이션 파일을 검토합니다.

  3. 데이터베이스에 마이그레이션을 적용합니다.

    python manage.py migrate myapp
    
  4. 마이그레이션 상태 확인:

    python manage.py showmigrations myapp
    

초기 프로젝트 설정

SQL Server 사용하여 새 Django 프로젝트를 설정하는 경우 마이그레이션을 실행하여 Django의 기본 제공 테이블(인증, 세션, 관리자)을 만듭니다.

python manage.py migrate

이 명령은 에 나열된 INSTALLED_APPS앱에 필요한 모든 테이블을 만듭니다.

마이그레이션의 사용자 지정 SQL

마이그레이션하는 동안 원시 SQL 문을 실행하는 데 사용합니다 migrations.RunSQL . 이 방법은 저장 프로시저, 트리거 또는 기타 SQL Server 특정 개체를 만드는 데 유용합니다.

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;",
        ),
    ]

이동 예외 사례

다음 마이그레이션 작업에는 SQL Server 대상으로 지정할 때 해결 방법이 필요합니다.

AutoField 변경

마이그레이션 시 모델 필드 AutoField 의 변경은 지원되지 않습니다. SQL Server 기존 열에서 속성을 추가하거나 제거할 IDENTITY 수 없습니다.

해결 방법: 원하는 필드 형식으로 새 모델을 만듭니다. 이전 테이블에서 새 테이블로 데이터를 마이그레이션한 다음 이전 테이블을 삭제합니다.

외래 키 제약 조건이 있는 필드 또는 모델 이름 바꾸기

외래 키 제약 조건이 있는 필드 또는 모델의 이름을 바꾸면 실패할 수 있습니다. SQL Server 이름 바꾸기 작업 중에 FK 제약 조건을 삭제하고 다시 만들어야 합니다.

해결 방법: Django에게 모델 상태를 업데이트하라고 말하면서 FK 제약 조건을 삭제하고, 열 이름을 바꾸고, 제약 조건을 다시 만드는 데 사용합니다 migrations.SeparateDatabaseAndState . 다음 예제에서는 모델의 외래 키 product 이름을 다음과 같이 Order바꿉니다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",
                ),
            ],
        ),
    ]

이 Transact-SQL(T-SQL) 코드를 실행하기 전에 데이터베이스에서 실제 제약 조건 이름을 찾아보세요. Django는 짧은 해시를 포함하는 제약 조건 이름을 생성하므로 스키마의 이름이 여기에 표시된 자리 표시자와 일치하지 않습니다.

데이터베이스 수준의 참조 동작

Django 6.1은 , DB_SET_NULL, 와 DB_SET_DEFAULT같은 DB_CASCADE데이터베이스 수준의 참조 동작을 추가합니다. SQL Server는 동일한 테이블에 여러 개의 캐스케이드 경로를 가진 외래 키 그래프를 거부하기 때문에 mssql-django 어떤 SQL Server 버전에서도 이러한 값을 지원하지 않습니다. 이 값들 중 하나를 사용하면 Django 시스템 검사 fields.E324가 올라갑니다. 대신 표준 Django 수준의 on_delete 동작을 사용하세요.

비트 단위 집계

Django 6.1은 BitAnd, BitOr, 그리고 BitXor를 추가합니다. SQL Server에는 네이티브 비트 집합 함수가 없으며mssql-django, 이러한 집합체를 에뮬레이션하지 않습니다. 그것들을 호출하면 NotSupportedError이 증가합니다.

스쿼시 마이그레이션

많은 마이그레이션이 누적되면 더 적은 수의 파일로 스쿼시할 수 있습니다.

python manage.py squashmigrations myapp 0001 0010

Tip

항상 새 데이터베이스에 대해 스쿼시된 마이그레이션을 테스트하여 올바른 스키마를 생성하도록 합니다.

생성된 열(계산 열)

백엔드는 SQL Server 계산 열에 매핑되는 Django의 GeneratedField를 지원합니다.

저장된(PERSISTED) 생성된 열

저장된 생성된 열은 디스크에 물리적으로 쓰여지고 원본 열이 변경될 때 업데이트됩니다.

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

다음을 생성합니다: [total_price] AS (([price] * (1 + [tax_rate]))) PERSISTED SQL Server는 표현식을 저장할 때 정규화하므로, sys.computed_columns는 ([price]*((1)+[tax_rate]))를 보고합니다.

가상 생성 열

가상 생성 열은 쿼리 시 계산되며 스토리지를 소비하지 않습니다.

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

메모

SQL Server는 비영구 계산 열에 대한 인덱스 생성을 제한합니다. 생성된 열을 인덱싱해야 하는 경우 사용합니다 db_persist=True .

테이블 및 열 주석

mssql-django 백엔드는 지원되는 Django 버전에서 Django의 db_comment 기능을 지원합니다. 주석은 SQL Server 개체에 확장 속성으로 MS_Description 저장됩니다.

테이블 주석

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."

열 주석

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

주석은 SQL Server Management Studio의 열/테이블 속성에서 그리고 sys.extended_properties를 통해 볼 수 있습니다.

복합 기본 키

Django 5.2가 도입되었습니다 CompositePrimaryKey. mssql-django 백 엔드는 복합 기본 키를 부분적으로 지원하지만 일부 Django 테스트 사례는 여전히 제외됩니다. 프로덕션 환경에서 채택하기 전에 애플리케이션에 대한 복합 키 마이그레이션 및 쿼리의 유효성을 검사합니다.

  • inspectdb 는 복합 기본 키를 올바르게 생성하지 않습니다. 검사 후 수동으로 정의합니다.
  • 튜플 조회는 지원되지 않습니다. 백엔드는 복합 키 비교를 개별 열 조건으로 분해합니다.
  • 하위 쿼리에 대한 튜플 비교에는 Django 5.2.4 이상 버전이 필요합니다.
  • 일부 마이그레이션 작업에는 여전히 알려진 제외 사항이 있습니다. 현재 상태는 mssql-django의 제한 사항 및 지원되지 않는 기능을 참조하세요.
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 처리

명시적 값을 AutoField 삽입할 때(예: 특정 ID를 사용하여 백업에서 데이터 복원) 백 엔드는 삽입 SET IDENTITY_INSERT ON / SET IDENTITY_INSERT OFF을 자동으로 래핑합니다. 수동 SQL이 필요하지 않습니다.

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

메모

SQL Server에서는 세션당 한 번에 하나의 테이블에만 IDENTITY_INSERT ON 있을 수 있습니다. 단일 atomic() 블록의 여러 테이블에 명시적 ID를 삽입하는 경우 백 엔드는 문당 토글을 처리합니다. 그러나 동일한 테이블에서도 사용하는 IDENTITY_INSERT 동시 세션은 충돌할 수 있습니다.