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

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

스쿼시 마이그레이션

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

python manage.py squashmigrations myapp 0001 0010

Tip

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

생성된 열(계산 열)

백엔드는 SQL Server의 계산된 열에 매핑되는 Django의 GeneratedField (Django 5.0 이상)를 지원합니다.

저장된(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

가상 생성 열

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

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의 db_comment 기능(Django 4.2 이상)을 지원합니다. 주석은 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 동시 세션은 충돌할 수 있습니다.