sqlalchemy uselist와 참조무결성 on postgresql
python으로 된 백엔드 프레임 워크는 django를 사용하고 있습니다.
최근 몇가지 문제로 다른 프레임워크에 대해 살펴보고 있구요.
후보군으로는 golang의 gingonic과 python의 fastAPI입니다.
프레임워크의 이동은 간단한 것이아니기 때문에 다양한 기술에대해 살펴볼 필요가 있습니다.
최근 뜨고있는 fastAPI를 살펴보면서 ORM으로 sqlalchemy에 대해 알 필요가 있다고 여겨져서 이리저리 살펴보는 중입니다.
사실 django에서도 raw Query로 대부분을 개발해왔기 때문에 raw Query가 편하긴 합니다. 하지만 개발 속도와 가독성있고 안정적인 코드를 위해 약간의 성능을 포기하는 것도 나쁘지 않다고 생각되었네요.
어쨌든 sqlalchemy에 대한 해외 문서들을 살펴보던 중 1:1 관계에서 uselist를 사용하는 사례를 보게되었습니다.
1:1관계는 흔한 관계는 아니기 때문에 우선은 무시하고 지나가도 되겠지만, 현재 개발하는 프로젝트에서 1:1 관계가 여럿 등장하기 때문에 정리하고자 글을 남깁니다.
# uselist 예제
class Member(Base):
__tablename__ = 'member'
id = Column(Integer, primary_key = True)
name = Column(String(256))
class Address(Base):
__tablename__ = 'address'
id = Column(Integer, primary_key = True)
address = Column(String(256))
member_id = Column(Integer, ForeignKey('member.id'))
member = relationship(Member, backref=backref('address', uselist=False))
이 코드에서 주목할 부분은 마지막 줄에 uselist=False
입니다.
두 개의 테이블이 생성되었고, address 테이블의 member_id
가 외래키로 지정되어 member의 primary_key
를 참조하고 있습니다.
사실 단순히 db에만들어진 테이블로만 생각하면 위 관계는 1:1이아니고 1:다 (member: address) 의 관계가 됩니다.
이유는 다음과 같습니다.
- 우선 애초에 다:다 관계를 두 테이블 상에서 나타낼 수는 없습니다. 따라서 두 테이블의 관계는 1:1 또는 1:다 관계가 됩니다.
- address의
member_id
는 member테이블의id
값을 중복해서 가질 수 있습니다. 따라서 하나의 member인스턴스가 여러 address인스턴스에 대응될 수 있습니다.
따라서 위 코드에서는 1:1관계로 만들기 위해 uselist=False
로 만들었습니다.
하지만 uselist=False
는 1:1아닌 1: (0..1) 관계를 만듭니다.
실제 동작은 다음과 같습니다.
- address 테이블에 동일한
member_id
가 입력되려고 한다. - 동일한
member_id
입력에 대해서는member_id
를 null로 만들고 나머지 값들은 그대로 입력한다.
따라서null로 인해 참조무결성이 희생됩니다.
더 큰 문제는 uselist=True
였던 테이블을 uselist=False
로 변경한 경우입니다.
이런 경우는 데이터의 입력이 이미 이루어 졌고 입력 데이터 자체에 1:다의 관계가 형성되었다면 참조 무결성은 무참히 깨지게 됩니다.
왜냐면 uselist=False
자체로 테이블의 참조 무결성을 검사하지 않고 에러를 내지 않기 때문입니다. 몇몇 예제로 실험해보니 이 옵션은 단지 db단이 아닌 python코드 단에서 필터링하는 효과만 있는 것 같습니다.
저도 처음으로 문서를 살펴보는 중이므로 저 옵션을 어떻게 사용해야할지 궁금합니다.
postgresql이 아닌 다른 데이터베이스(mysql, SQLite)과 같은 경우에 필요한 옵션이라고 생각합니다.
하지만 1:1관계는 그렇게 흔한 관계는 아니므로 다음과 같은 결론을 내고 마무리하려고 합니다.
- 참조 무결성을 위해서는 Foreign Key(member_id)의 제약조건에
unique=True
옵션을 주는 것이 좋다고 생각합니다. - 만약
uselist=False
옵션을 사용한다면nullable=False
도 같이 주는 것이 좋다고 생각합니다. 하지만 기존 테이블이uselist=True
였다면 참조 무결성을 보장할 수 없으므로unique=True
옵션을 주고 데이터 참조 무결성을 해결하는 것이 좋은 해결책일 듯 싶습니다.