회사에서 이슈 진행을 하다가, 트랜잭션를 이용하여 처리해야하는 부분을 발견하였다.
따라서 SQLAlchemy
에서는 트랜잭션을 어떻게 사용하는지 정리해보도록 하겟다.
트랜잭션 관리하기
- 새롭게 생성된 세션은
begin()
상태이다. begin()
상태에서Session
은 아직 어떠한Connection
및Transactional
과 연관되지 않았다.- 그러한 다음에,
Session
은 데이터베이스 커넥션 요청을 수신한다. - 일반적으로, 이것은
Engine
을 이용하여, 특정SQL
문을 수행해야하는 것을 의미한다. - 특정
SQL
문의 수행은Session.query()
,Session.execute()
통해 이루어지고Session.commit()
,Session.flush()
를 할 때 보류된 변경 사항을 비우고 커밋하면서 발생합니다. - 이러한 요청이 수신되면, 새로운 엔진 각각이 세션에서 유지되고 관리하는 트랜잭션 상태와 연결이 된다.
- 첫 번째, 엔진이 작동하면 세션은
begin
상태를 떠나서,transactional
상태로 전환되었다고 할 수 있다. - 각각의 엔진에 대해서 연결이 되어 있으며, 이러한 연결은
Engine.contextual_connect()
메서드를 통해서 획득된다. - 롤백 또는 커밋 후에 트랜잭션 상태가 완료되면 모든 트랜잭션 및 연결 리소스를 해제하고,
begin
상태로 돌아간다. begin
상태로 돌아가면, 다시 새로운SQL
문을 내보내는 새 요청이 수신될 때 새로운Connection
및Transaction
객체를 다시 호출합니다.
다음은 위의 라이프 사이클을 보여주는 예제이다.
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
-
Session.begin_nested()
는 여러 번 호출 될 수 있으며, 각 호출에 대해서 고유한 식별자가 있는 새로운 SAVEPOINT를 발행합니다. -
각각의
Session.begin_nested()
호출에 대해서,Session.rollback()
또는,Session.commit()
을 수행 해야 합니다. -
그러나 반환 값이 컨텍스트 매니저에 의해서 사용되는 경우 예를 들어서
with
문에서rollback
및commit
은 컨텍스트를 종료할 때, 컨텍스트 관리자가 실행하므로 명시적으로 추가해서는 안된다. -
Session.begin_nested()
메서드는Session.begin()
을 덜 사용하기 위해서 사용하며 컨텍스트 매니저로 작동하는SessionTransaction
을 반환한다. -
개발 레코드를 삽입할 때, 유용하게 사용할 수 있다.
for record in records:
try:
with session.begin_nested():
session.merge(record)
except:
print("Skipped record %s" % record)
session.commit()
트랜잭션 격리 수준 설정 / DBAPI AUTOCOMMIT
- 대부분의
DBAPI
는 설정할 수 있는 트랜잭션 격리 수준의 개념을 지원한다. - 전통적으로
READ UNCOMMITED
,READ COMMITED
,REPEATABLE READ
, 및SERIALIZABLE
의 네 가지 수준이 있다. - 이는 일반적으로 새로운 트랜잭션을 사용하기 전에,
DBAPI
연결에 적용되며 대부분의 DBAPI는 SQL문이 처음 생성될 때, 트랜잭션을 암시적으로 시작한다. SQLAlchemy
는create_engine
수준과,Connection.execution_options()
수준 모두에서 플래그를 사용하여 엔진 별 또는 연결 별로 설정 가능한 격리 모드를 지원한다.
세션 메이커 및 엔진 전체에 대한 고립 수준 설정
전역 적으로 특정 고립 수준으로 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)
- 위의
eng
과autocommit_engine
은 동일한 연결 풀을 공유합니다. AUTOCOMMIT
모드는autocommit_engine
에서 연결할 때 따로 설정된다.
각각의 세션에 대한, 고립 수준 설정
생성자를 직접 사용하거나, 세션 메이커가 생성한 호출 가능한 객체를 호출 할 때, bind
인자로 바로 전달 할 수 있다.
예를 들어, transactional_session
에서 세션을 만들고, autocommit_engine
을 전달할 수 있다.
session = transactional_session(bind=autocommit_engine)
# work with session
session.close()
각각의 트랜잭션에 대한 고립 수준 설정
격리 수준과 관련된 주요한 주의 사항은 트랜잭션이 이미 시작된 연결에서 설정을 안전하게 수정할 수 없다는 것이다.
데이터베이스는 진행중인 트랜잭션의 격리 수준을 변경할 수 없으며 일부 DBAPI
및 SQLALchemy
언어에서는 이러한 영역에서 일관적이지 않은 동작이 있다.
따라서 원하는 격리 수준을 가진 엔진 앞쪽에 바인딩 된 세션을 사용하는 것이 좋다.
그러나 연결 단위의 격리 수준은 트랜잭션이 시작될 때 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()
을 호출하여 트랜잭션 시작을 명시적으로 설정한다.