SQLAlchemy에서 트랜잭션 사용법

이동욱

2021/03/18

Categories: 데이터베이스 Tags: 데이터베이스

회사에서 이슈 진행을 하다가, 트랜잭션를 이용하여 처리해야하는 부분을 발견하였다. 따라서 SQLAlchemy에서는 트랜잭션을 어떻게 사용하는지 정리해보도록 하겟다.

트랜잭션 관리하기

Screen Shot 2021-03-18 at 1 56 50 PM

다음은 위의 라이프 사이클을 보여주는 예제이다.

engine = create_engine("...")
Session = sessionmaker(bind=engine)

# 새로운 세션, 어떤 커넥션도 사용중이지 않다.
session = Session()
try:
    # 첫 번째 쿼리를 수행하면, 커넥션을 엔진으로 부터 획득하고
    # 트랜잭션이 시작된다.    
    item1 = session.query(Item).get(1)

    # 두 번째 쿼리이다. 같은 커넥션과 트랜젝션이 사용된다.
    item2 = session.query(Item).get(2)

    # 아직 반영되지 않은 변경 사항이 생성된다.
    item1.foo = 'bar'
    item2.bar = 'foo'
    
    # 커밋을 수행한다.
    # 아직 반영되지 않은 변경사항이 모두 `flush` 된다.
    # 트랜잭션이 커밋되고, 연결 객체가 닫치고 사라진다.
    # DBAPI 연결이 커넥션 풀로 반환된다.
    session.commit()
except:
    # 롤백시에도, 커미과 동일한 상태 종료가 진행된다.
    session.rollback()
    raise
finally:
    # 세션을 닫는다, 이렇게 하면 남아 있는 모든 객체가 영구적으로 삭제되며, 기존 SessionTransaction 상태가 재설정 된다.
    # 일반적으로 이러한 단계는 필수는 아니지만,
    # commit() 또는 rollback() 자체에 예기치 않은 내부 오류가
    # 발생한 경우 close()는 유효하지 않은 상태가 제거되도록 한다.
    session.close()

SAVEPOINT 사용하기


기본 엔진에서 지원하는 경우, SAVEPOINT 트랜잭션은 Session.begin_nested() 메서드를 사용할 수 있다.

Session = sessionmaker()
session = Session()
session.add(u1)
session.add(u2)

session.begin_nested() # establish a savepoint
session.add(u3)
session.rollback()  # rolls back u3, keeps u1 and u2

session.commit() # commits u1 and u2
for record in records:
    try:
        with session.begin_nested():
            session.merge(record)
    except:
        print("Skipped record %s" % record)
session.commit()

트랜잭션 격리 수준 설정 / DBAPI AUTOCOMMIT


Screen Shot 2021-03-18 at 4 20 02 PM

세션 메이커 및 엔진 전체에 대한 고립 수준 설정

전역 적으로 특정 고립 수준으로 Session 또는 Session Maker를 설정하려면 첫 번째 기술은 모든 경우에 특정 격리 수준에 대해서 엔진을 설정한 다음에, 세션 메이커의 연결 소스로 사용하는 것이다.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

eng = create_engine(
    "postgresql://scott:tiger@localhost/test",
    isolation_level='REPEATABLE READ'
)

Session = sessionmaker(eng)

서로 다른 격리 수준을 가진 두 개의 엔진이 있는 경우 유용한 다른 옵션은 Engine.execution_options() 메서드를 사용하는 것입니다.

from sqlalchemy import create_engine
from sqlalchemy.orm import sessionmaker

eng = create_engine("postgresql://scott:tiger@localhost/test")

autocommit_engine = eng.execution_options(isolation_level="AUTOCOMMIT")

transactional_session = sessionmaker(eng)
autocommit_session = sessionmaker(autocommit_engine)

각각의 세션에 대한, 고립 수준 설정

생성자를 직접 사용하거나, 세션 메이커가 생성한 호출 가능한 객체를 호출 할 때, bind 인자로 바로 전달 할 수 있다. 예를 들어, transactional_session에서 세션을 만들고, autocommit_engine을 전달할 수 있다.

session = transactional_session(bind=autocommit_engine)
# work with session
session.close()

각각의 트랜잭션에 대한 고립 수준 설정

격리 수준과 관련된 주요한 주의 사항은 트랜잭션이 이미 시작된 연결에서 설정을 안전하게 수정할 수 없다는 것이다.

데이터베이스는 진행중인 트랜잭션의 격리 수준을 변경할 수 없으며 일부 DBAPISQLALchemy 언어에서는 이러한 영역에서 일관적이지 않은 동작이 있다.

따라서 원하는 격리 수준을 가진 엔진 앞쪽에 바인딩 된 세션을 사용하는 것이 좋다.

그러나 연결 단위의 격리 수준은 트랜잭션이 시작될 때 Session.commection() 메서드를 사용하여 영향 받을 수 있다.

from sqlalchemy.orm import Session

sess = Session(bind=engine)
with sess.begin():
    sess.connection(execution_options={'isolation_level': 'SERIALIZABLE'})

# commits transaction.  the connection is released
# and reverted to its previous isolation level.

위에서 먼저, 생성자 또는 세션 메이커를 사용하여 세션을 생성한다. 그런 다음에 트랜잭션이 시작되기 전에 연결에 전달되는 실행 옵션을 제공하는 Session.connection()을 호출하여 트랜잭션 시작을 명시적으로 설정한다.

참고 문헌

>> Home