기존 데이터베이스를 django로 운영하고자 할 때 통합하기 위해선 inspectdb 를 이용할 수 있습니다.
inspectdb라는 유틸리티를 통해 생성된 모델들을 해당 프로젝트 setting.py
에 app 패키지에 추가시키고 managed 옵션을 주어 관리하게 됩니다. 라이프사이클 관리를 허용할 때는 True, False이며 모든 앱들의 수정이 완료되면 migrate 명령을 통해 django 테이블 추가 및 통합을 이루게 됩니다.
이렇게 초기에 통합 환경을 만들고 운영하던 중 특정 모델의 추가적로 컬럼(FK)을 추가해야하는 상황에서 발생한 문제와 해결 과정을 정리해보고자 합니다.
1. 문제 발생
- 기존 레거시 mysql 데이터베이스에 inspectdb 이후 프로젝트에 기존 Model에 FK 컬럼을 추가해야하는 이슈 발생
- Model에 Field 추가 후
makemigrations 앱이름
- migrations 파일을 기반으로
migrate 앱이름
errno: 150 "Foreign key constraint is incorrectly formed"
에러 발생
2. 해결 방법
2-1. "Foreign key constraint is incorrectly formed"
위에 에러같은 경우는 평소에도 간혹 확인할 수 있는 에러 메세지입니다.
검색을 통해 찾아봤을 때 해당 문제는 크게 다음과 같았습니다.
- 참조 받는 필드가 pk or unique 여부 확인
- 추가시키는 필드와 추가시킬 필드가 참조 받는 필드의 관계에 not null, null 여부가 동일한지 확인
- 추가시키는 필드와 추가시킬 필드가 참조 받는 필드의 관계에 data type 여부가 동일한지 확인
2-2. 추가시키는 필드와 추가시킬 필드가 참조 받는 필드의 관계에 data type 여부가 동일한지 확인
위 2가지의 항목은 문제가 아닌 상태를 확인 후 마지막을 확인할 순서였습니다.
에러가 발생한 migrations 파일의 sql을 python manage.py sqlmigrate 앱이름 마이그레이션파일명
명령을 통해 확인해봤습니다.
id INT(11) AUTO_INCREMENT
sql 명령을 확인할 수 있었고 migrations/0001_initial
파일을 살펴보니 AutoField로 생성되고 있었습니다.
default, Django creates an INT(11) id field to handle models primary keys
기존 레거시 테이블의 경우 static 테이블에 가까워 많은 데이터가 필요하지 않았기에 pk가 mediumint를 사용하고 있었고 이 부분을 의심하게 되었습니다.
필드를 추가하는 DDL sql 명령을 실행하니 잘 동작한 것을 확인 후 추가적으로 테스트를 위해 해당 테이블의 pk를 Int(11)로 변경 후 migrate 명령을 실행하니 제대로 동작하는 것을 확인하였습니다.
하지만 기존 테이블의 id 데이터 타입을 변경하고 싶지 않았기에 django로 관리하는 모델의 커스텀 필드가 있지 않을까 검색을 하게 되었습니다.
결과적으로 django 공식 홈페이지에 해당 내용을 확인할 수 있었습니다.
2-3. custom model fields
사용자 맞춤 정의 DB 유형중 데이터 타입을 구현하는 파트를 확인할 수 있었습니다.
커스텀 필드가 특정 테이블에 필드를 참조하는 경우와 그렇지 않은 경우 크게 2가지로 구분되어 있었습니다. 저의 경우 전자의 상황이었기에 아래와 같이 내용을 확인할 수 있었습니다.
- 내가 사용할 커스텀 필드 클래스를 정의한다.
- 해당 클래스의 내부에 db_type, rel_db_type 함수를 선언한다.
- unsigned의 경우 AutoField에서 동일한 데이터 유형을 사용할 시 해당 필드를 가리키는 외래 키 함수도 필요
# MySQL unsigned integer (range 0 to 4294967295).
class UnsignedAutoField(models.AutoField):
def db_type(self, connection):
return 'integer UNSIGNED AUTO_INCREMENT'
def rel_db_type(self, connection):
return 'integer UNSIGNED'
# 우리가 사용할 커스텀 구현
class CustomMediumAutoField(models.AutoField):
def db_type(self, connection):
return 'mediumint(8) UNSIGNED AUTO_INCREMENT'
def rel_db_type(self, connection):
return 'mediumint(8) UNSIGNED'
- 해당 앱 migrations/initial 0001_init 파일에 fields에 models.AutoField를 custom으로 생성된 클래스로 data-type 변경
- 'id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
- 'id', models.CustomMediumAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
- django 모델에서 id를 해당 위와 함수로 변경한다.
- 이때 함수와 동일하게 넘겨주는 파라미터 순서까지 맞춰주어야 합니다. 안그러면 AutoField로 alter 쿼리가 생성됩니다.
- 기존에 선언되어 있다면 해당 id Field를 수정, 선언되지 않으면 명시적으로 만든 data-type으로 변경
$ python manage.py makemigrations 앱이름
$ python manage.py sqlmigrate 앱이름 생성된 migrations 파일
ALTER TABLE 추가할 테이블명 ADD COLUMN 추가할 컬럼명 mediumint(8) UNSIGNED NULL , ADD CONSTRAINT 참조이름 FOREIGN KEY (추가할 컬럼명) REFERENCES 테이블명 (id)
;$ python manage.py migrate 앱이름
3. 결과
class Tag(models.Model):
id = CustomMediumAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')
name = models.CharField(unique=True, max_length=32)
parent_tag = models.ForeignKey("self", on_delete=models.SET_NULL, null=True, db_column='실제 DB Column명')
Reference
'Posts > Django' 카테고리의 다른 글
실서비스에 Django ORM API 삽질 + 성능 개선기 (0) | 2021.12.23 |
---|---|
Django 실제 서버에 왜 runserver를 하지 않고 웹서버+wsgi를 사용할까? (0) | 2021.11.14 |