본문 바로가기
Django/Django개념

django transaction(장고 트랜잭션)

by hyun-am 2022. 8. 25.

트랜잭션

 

트랜잭션이란

  • 데이터베이스 관리 시스템 또는 유사한 시스템에서 상호작용의 단위입니다.
  • 여기서 유사한 시스템이란 트랜잭션이 성공과 실패가 분명하고 상호 독립적이며, 일관되고 믿을수 있는 시스템을 의미합니다.

트랜잭션의 목적

  • 사용자가 데이터베이스 완전성(integrity)유지를 확신하게 합니다.
  • 데이터베이스 서버에 여러 개의 클라이언트가 동시에 액세스 하거나 응용프로그램이 갱신을 처리하는 과정에서 중단될 수 있는 경우 등 데이터 부정합을 방지하고자 할 때 사용합니다.

트랜잭션 예시

A라는 사람이 B라는 사람에게 1,000원을 지급하고 B가 그 돈을 받은 경우, 이 거래 기록은 더 이상 작게 쪼갤 수가 없는 하나의 트랜잭션을 구성한다. 만약 A는 돈을 지불했으나 B는 돈을 받지 못했다면 그 거래는 성립되지 않는다. 이처럼 A가 돈을 지불하는 행위와 B가 돈을 받는 행위는 별개로 분리될 수 없으며 하나의 거래내역으로 처리되어야 하는 단일 거래이다. 이런 거래의 최소 단위를 트랜잭션이라고 한다. 트랜잭션 처리가 정상적으로 완료된 경우 커밋(commit)을 하고, 오류가 발생할 경우 원래 상태로 롤백(rollback)을 한다.

트랜잭션 ACID

  • 원자성(Atomicity) : 트랜잭션과 관련된 작업들이 부분적으로 실행되다가 중단되지 않는 것을 보장하는 능력.
  • 일관성(Consistency) : 트랜잭션 실행 시 성공적으로 완료하면 언제나 일관성 있는 데이터베이스 상태로 유지하는 것
  • 독립성(Isolation) : 트랜잭션 수행 시 다른 트랜잭션의 연산 작업이 끼어들지 못하도록 보장하는 것
  • 지속성(Durability) : 성공적으로 수행된 트랜잭션은 영원히 반영되어야합니다. 전형적으로 모든 트랜잭션은 로그로 남고 시스템 장애 발생 전 상태로 되돌릴 수 있습니다. 트랜잭션은 로그에 모든 것이 저장된 후에만 commit상태로 간주합니다.

Django 트랜잭션 기본동작

Django의 기본 동작은 autocommit mode에서 실행하는 것입니다. 트랜잭션이 활성 상태가 아닌 한 각 쿼리는 즉시 데이터베이스에 커밋됩니다.

Django는 transactions 또는 savepoints를 자동으로 사용하여 여러 쿼리, 특히 delete() 및 update() 쿼리가 필요한 ORM 작업의 무결성을 보장합니다.

Django의 TestCase 클래스는 또한 성능상의 이유로 트랜잭션에서 각 테스트를 래핑합니다.

HTTP요청에 transactions 구현하기

웹에서 트랜잭션을 처리하는 일반적인 방법은 각 요청을 트랜잭션으로 래핑하는 것입니다. 이 동작을 활성화하려는 각 데이터베이스의 구성에서 ATOMIC_REQUESTS를 True로 설정합니다.

 

뷰 함수를 호출하기 전에 Django는 트랜잭션을 시작합니다. response가 문제없이 생성되면 Django는 트랜잭션을 커밋합니다. view에서 예외가 발생하면 Django는 트랜잭션을 롤백합니다.

 

일반적으로 atomic() 컨텍스트 관리자와 함께 view 코드의 savepoint를 사용하여 하위 트랜잭션을 수행할 수 있습니다. 그러나 view가 종료되면 변경사항이 모두 commit되거나 전혀 commit되지 않습니다.

 

<주의사항>

이 트랜잭션 모델의 단순성은 매력이지만 트래픽이 증가하면 비효율적이기도 합니다. 모든 view에 대해 트랜잭션을 열어두면 약간의 오버헤드가 있을 수 있습니다. 성능에 미치는 영향은 애플리케이션의 쿼리 패턴과 데이터베이스가 lock을 얼마나 잘 처리하는지에 따라 다릅니다.

 

<요청당 트랜잭션 및 스트리밍 응답>

view가 StreamingHttpResponse를 반환할 때 response의 내용을 읽으면 내용을 생성하는 코드가 실행되는 경우가 많습니다. view가 이미 반환되었기 때문에 이러한 코드는 트랜잭션 외부에서 실행됩니다. 일반적으로 응답을 보내기 시작한 후 오류를 처리하는 합리적인 방법이 없기 때문에 스트리밍 응답을 생성하는 동안 데이터베이스에 쓰는 것은 권장하지 않습니다.

 

실제로 이 기능은 아래에 설명된 atomic() 데코레이터의 모든 view 기능을 래핑합니다.

view실행만 트랜잭션에 포함됩니다. 미들웨어는 트랜잭션 외부에서 실행되며 템플릿 response의 렌더링도 마찬가지입니다.

ATOIC_REQUESTS가 활성화되면 트랜잭션에서 뷰가 실행되는 것을 방지할 수 있습니다.

 

non_atomic_requests(using=None)[source]

이 데코레이터는 주어진 view에 대한 ATOMIC_REQUESTS의 효과를 무효화합니다.

from django.db import transaction

@transaction.non_atomic_requests
def my_view(request):
    do_stuff()

@transaction.non_atomic_requests(using='other')
def my_other_view(request):
    do_stuff_on_the_other_database()

명시적으로 트랜잭션 제어하기

django database는 트랜잭션을 제어하는 단일 API를 제공합니다.

 

atomic(using=None, savepoint=True, durable=False)

원자성은 데이터베이스 트랜잭션의 정의 속성입니다. atomic을 사용하면 데이터베이스의 원자성이 보장되는 코드블록을 만들 수 있습니다. 코드블록이 성공적으로 완료되면 변경 내용이 데이터베이스에 커밋됩니다. 예외가 있는 경우 변경 내용이 롤백됩니다.

 

atomic block은 중첩될 수 있습니다. 이 경우, 내부 블록이 성공적으로 완료되었을 때, 나중에 외부 블록에서 예외가 발생하더라도 그 효과는 여전히 롤백될 수 있습니다.

 

때때로 atomic block을 항상 가장 바깥쪽 atomic block으로 설정하여 block이 오류 없이 종료될 때 데이터베이스 변경이 커밋되도록 하는 것이 유용합니다. 이를 **durable(지속성)**이라고하며 durable=True를 설정하여 달성할 수 있습니다. atomic block이 다른 블록 내에 중첩되면 런타임 오류가 발생합니다.

 

atmoic은 데코레이터로서 둘다 사용할 수 있습니다.

from django.db import transaction

@transaction.atomic
def viewfunc(request):
    # This code executes inside a transaction.
    do_stuff()

context manager로 사용할 경우

from django.db import transaction

def viewfunc(request):
    # This code executes in autocommit mode (Django's default).
    do_stuff()

    with transaction.atomic():
        # This code executes inside a transaction.
        do_more_stuff()

try/except block에서 atomic을 래핑하면 무결성 오류(Integrity error)를 자연스럽게 처리할 수 있습니다.

from django.db import IntegrityError, transaction

@transaction.atomic
def viewfunc(request):
    create_parent()

    try:
        with transaction.atomic():
            generate_relationships()
    except IntegrityError:
        handle_exception()

    add_children()

이 예시에서는 무결성 제약 조건을 해제하여 generate_relations()가 데이터베이스 오류를 발생시키더라도 add_children()에서 쿼리를 실행할 수 있으며 create_parent()의 변경 내용은 그대로 유지되고 동일한 트랜잭션에 바인딩됩니다. handle_exception()이 이미 호출될 때 generate_relationships()에서 시도한 모든 작업은 이미 안전하게 롤백되어 있으므로 예외처리기는 필요한 경우 데이터베이스에서도 작동할 수 있습니다.

 

<atmoic 내에서 예외가 발생하는것 피하기>

 

atomic block을 종료할 때 Django는 정상적으로 종료되었는지 아니면 예외 없이 종료되었는지 확인하여 commit 또는 rollback여부를 결정합니다. 원자 블록 내에서 예외를 포착하여 처리하는 경우 문제가 발생했다는 사실을 Django로 부터 숨길 수 있습니다. 이로 인해 예기치 않은 동작이 발생할 수 있습니다.

 

이는 대부분 DatabaseError 및 IntegrityError와 같은 하위 클래스에 대한 우려 사항입니다. 이러한 오류가 발생하면 트랜잭션이 중단되고 Django는 원자 블록의 끝에서 롤백을 수행합니다. 롤백이 발생하기 전에 데이터베이스 쿼리를 실행하려고 하면 Django에서 트랜잭션 관리 오류가 발생합니다. ORM 관련 signal handler가 예외를 발생시킬 때도 이 동작이 발생할 수 있습니다.

데이터베이스 오류를 정확하게 포착하는 방법은 위와 같이 atomic block 주변에 있습니다. 필요한 경우 이목적을 위해 추가 atomic block을 추가합니다. 이 패턴에는 예외가 발생할 경우 롤백될 작업을 명시적으로 제한하는 또 다른 장점이 있습니다. Raw SQL 쿼리에서 발생한 예외를 포착하면 Django의 동작은 지정되지 않고 데이터베이스에 종속됩니다.

 

<트랜잭션을 롤백할 때 모델 상태를 수동으로 되돌려야 할 수 있습니다>

트랜잭션 롤백이 발생할 때 모델의 필드 값은 반환되지 않습니다. 이렇게 하면 원래 필드 값을 수동으로 복원하지 않으면 모델 상태가 일관되지 않을 수 있습니다.

예를 들어, 활성 필드가 있는 MyModel의 경우, 이 코드 조각은 트랜잭션에서 활성 업데이트가 True로 실패하는 경우 마지막에 ifobj.active 검사가 올바른 값을 사용하도록 보장합니다.

 

from django.db import DatabaseError, transaction

obj = MyModel(active=False)
obj.active = True
try:
    with transaction.atomic():
        obj.save()
except DatabaseError:
    obj.active = False

if obj.active:
    ...

원자성을 보장하기 위해 atomic은 일부 API를 비활성화합니다. 원자 블록 내에서 데이터베이스 연결의 자동 커밋, 롤백 또는 변경하려고 하면 예외가 발생합니다.

 

atomic은 데이터베이스의 이름이어야 하는 using argument를 사용합니다. 이 argument가 제공되지 않으면 장고는 “기본" 데이터베이스를 사용합니다.

 

후드 아래에는 장고의 트랜잭션 관리 코드가 있습니다.

  • 가장 바깥쪽 atomic block에 들어갈 때 트랜잭션을 엽니다.
  • 내부 atomic blcok에 들어갈 때 savepoints를 만듭니다.
  • 내부 block을 나갈 때 savepoints로 해제하거나 롤백합니다.
  • 가장 바깥족 block에서 나갈 때 트랜잭션을 커밋하거나 롤백합니다.

savepoint 아규먼트를 False로 설정하여 내부 블록에 대한 savepoint 생성을 비활성화할 수 있습니다. 예외가 발생하면 저장점이 있는 첫 번재 상위 블록에서 나갈 때 Django는 롤백을 수행하고 그렇지 않으면 가장 바깥쪽 블록에서 롤백을 수행합니다. atomic은 여전히 외부 transaction로 보장됩니다. 이 옵션은 savepoint의 오버헤드가 눈에 띄는 경우에만 사용해야 합니다. 위에서 설명한 오류 처리를 깨는 단점이있습니다.

자동 커밋이 해제된 경우 atomic을 사용할 수 있습니다. 가장 바깥쪽 블록에서도 savepoint만 사용됩니다.

 

<성능 고려 사항>

open transactions는 데이터베이스 서버의 성능 비용이 있습니다. 이러한 오버헤드를 최소화하려면 트랜잭션을 최대한 짧게 유지하면 되겠습니다. 이것은 특히 장고의 요청/응답 주기 이외의 장기 실행 프로세스에서 atomic()을 사용하는 경우에 중요합니다.

 

<주의 사항>

장고 테스트TestCase는 성능상의 이유로 트랜잭션에서 durable한 atomic block을 테스트할 수 있도록 durable 검사를 비활성화 합니다. django.test.TransactionTestCase를 사용합니다.

AutoCommit

왜 장고에서는 autocommit을 사용할까

SQL 표준에서 각 SQL 쿼리는 이미 활성 상태인 경우를 제외하고 트랜잭션을 시작합니다. 그런 다음 이러한 트랜잭션을 명시적으로 커밋하거나 롤백해야 합니다.

이것이 애플리케이션 개발자에게 항상 편리한 것은 아닙니다. 이 문제를 완화하기 위해 대부분의 데이터베이스는 자동 커밋 모드를 제공합니다. 자동 커밋이 설정되어 있고 활성 트랜잭션이 없는 경우 각 SQL 쿼리는 자체 트랜잭션으로 래핑됩니다. 다시 말해, 이러한 각 쿼리는 트랜잭션을 시작할 뿐만 아니라 쿼리의 성공 여부에 따라 트랜잭션이 자동으로 커밋되거나 롤백됩니다.

REP 249(Python Database API Specification v2.0)는 처음에 자동 커밋을 해제 해야합니다. Django는 이 기본값을 재정의하고 자동 커밋을 설정합니다.

이를 방지하려면 트랜잭션 설정을 비활성화 할 수 있지만 권장되지 않습니다.

Deactivating transaction management

구성에서 AUTOCOMMIT를 False로 설정하여 지정된 데이터베이스에 대한 Django의 트랜잭션 관리를 완전히 비활성화할 수 있습니다. 이렇게 하면 장고는 자동 커밋을 사용할 수 없으며 커밋을 수행하지 않습니다. 기본 데이터베이스 라이브러리의 규칙적인 동작을 볼 수 있습니다.

이를 위해서는 모든 트랜잭션을 명시적으로 커밋해야 합니다. 심지어 Django 또는 써드파티 라이브러리에 의해 시작된 트랜잭션도 커밋해야합니다. 따라서 트랜잭션 제어 미들웨어를 실행하거나 정말 이상한 작업을 할 때 가장 적합합니다.

Performing actions after commit(커밋 후 작업 수행)

트랜잭션이 성공적으로 커밋된 경우에만 현재 데이터베이스 트랜잭션과 관련된 액션을 수행해야 하는 경우가 있습니다. 예를 들어 셀러리 테스크, 전자 메일 통지 또는 캐시 무효화가 있습니다.

Django는 트랜잭션이 성공적으로 커밋된 후에 실행되어야 하는 콜백 함수를 등록하기 위한 on_commit() 함수를 제공합니다.

 

on_commit(func, using=None)

on_commit()에 아규먼트 없이 모든 함수를 전달합니다.

from django.db import transaction

def do_something():
    pass  # send a mail, invalidate a cache, fire off a Celery task, etc.

transaction.on_commit(do_something)

이러한 함수는 람다로도 처리할 수 있습니다.

transaction.on_commit(lambda: some_celery_task.delay('arg1'))

전달된 함수는 on_commit()이 호출된 가상 데이터베이스 쓰기가 성공적으로 커밋된 후 즉시 호출됩니다.

활성 트랜잭션이 없을 때 on_commit()을 호출하면 콜백이 즉시 실행됩니다.

savepoints

저장 지점(예 : nested atomic() blocks)이 올바르게 처리됩니다. 즉, 외부 트랜잭션이 커밋된 후 (nested atomic() block_에 있는 savepoints 뒤에 등록된 on_commit() 호출 가능 파일이 호출되지만, 트랜잭션 중에 해당 저장 지점 또는 이전 savepoint로 롤백이 발생한 경우에는 호출되지 않습니다.

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    with transaction.atomic():  # Inner atomic block, create a savepoint
        transaction.on_commit(bar)

# foo() and then bar() will be called when leaving the outermost block

반면, 예외가 발생했기 때문에 savepoint가 롤백되는 경우, inner callable은 호출되지 않습니다.

with transaction.atomic():  # Outer atomic, start a new transaction
    transaction.on_commit(foo)

    try:
        with transaction.atomic():  # Inner atomic block, create a savepoint
            transaction.on_commit(bar)
            raise SomeError()  # Raising an exception - abort the savepoint
    except SomeError:
        pass

# foo() will be called, but not bar()

Order of execution(실행순서)

지정된 트랜잭션에 대한 커밋 함수는 등록된 순서대로 실행됩니다.

Exceptioin handling(예외처리)

지정된 트랜잭션 내에서 하나의 온커밋 함수가 포착되지 않은 예외를 발생시키면 동일한 트랜잭션에 나중에 등록된 함수가 실행되지 않습니다. 이는 on_commit()없이 직접 함수를 순차적으로 실행한 것과 동일한 동작입니다.

Timing of execution(실행시기)

콜백은 커밋이 성공한 후에 실행되므로 콜백이 실패해도 트랜잭션이 롤백되지 않습니다. 그것들은 transaction의 성공에 따라 조건부로 실행되지만, 그것들은 transaction의 일부가 아닙니다.

 

의도된 사용 사례(메일 알림, 셀러리 작업 등)의 경우 이 방법이 좋습니다. 그렇지 않은 경우(팔로우업 작업이 너무 중요해서 실패가 트랜잭션 자체의 실패를 의미해야 하는 경우) on_commit() hook을 사용하지 않을 수 있습니다. 대신 pycopg two-phase commit 프로토콜 지원 및 Python DB-API 사양의 선택적 Two-Phase Commit Extensions와 같은 2단계 커밋을 원할 수 있습니다.

 

콜백은 커밋 후 연결에서 자동 커밋이 복원될 때까지 실행되지 않습니다.(그렇지 않으면 콜백에서 수행된 쿼리가 암시적 트랜잭션을 열어 연결이 자동 커밋 모드로 돌아가지 않도록 하기 때문입니다)

자동 커밋 모드이고 atomic() block 외부에 있는 경우, 함수는 커밋이 아닌 즉시 실행됩니다.

온 커밋 기능은 자동 커밋 모드와 Atomic()(또는 ATOMIC_REQUESTS)트랜잭션 API에서만 작동합니다. 자동 커밋을 사용하지 않고 원자 블록 내에 있지 않을 때 on_commit()을 호출하면 오류가 발생합니다.

use in tests

Django의 TestCase 클래스는 테스트 분리를 제공하기 위해 트랜잭션에서 각 테스트를 랩하고 각 테스트 후 해당 트랜잭션을 롤백합니다. 즉, 실제로 커밋된 트랜잭션이 없으므로 on_commit() 콜백이 실행되지 않습니다.

 

이 제한은 TestCase.captureOnCommitCallbacks()를 사용하여 해결할 수 있습니다. 그러면 on_commit() 콜백이 목록에 캡처되므로 콜백에 대한 주장을 하거나 콜백을 호출하여 트랜잭션 커밋을 에뮬레이트할 수 있습니다.

 

한계를 극복하는 또 다른 방법은 TestCase대신 TransactionTestCase를 사용하는 것입니다. 이는 트랜잭션이 커밋되고 콜백이 실행됨을 의미합니다. 그러나 TransactionTestCase는 테스트 사이에 데이터베이스를 flush하므로 TestCase의 격리보다 훨씬 느립니다.

Why no rollback hook?

rollback hook은 다양한 것들이 암묵적인 롤백을 일으킬 수 있기 때문에 커밋 hook보다 강력하게 구현하기가 어렵습니다.

예를 들어 프로세스가 정상적으로 종료되지 않아 데이터베이스 연결이 끊긴 경우 rollback hook은 실행되지 않습니다.

그러나 해결방법이 있습니다. atomic 블록(트랜잭션) 중에 작업을 수행한 다음 트랜잭션이 실패하면 실행 취소하는 대신 on_commit()를 사용하여 트랜잭션이 성공할 때까지 처음붜 실행을 지연시킵니다. 애초에 하지 않았던 일을 취소하는 것이 훨씬 더 쉽습니다.

Low-level APIs

가능하면 항상 atomic()을 선호합니다. 각 데이터베이스의 특성을 설명하고 잘못된 작업을 방지합니다.

low level의 API는 자체 트랜잭션 관리를 구현하는 경우에만 유용합니다.

Autocommit

Django는 django.db.transaction 모듈에 API를 제공하여 각 데이터베이스 연결의 자동 커밋 상태 관리합니다.

get_autocommit(using=None)

set_autocommit(autocommit,using=None)

 

이러한 함수는 데이터베이스 이름이어야 하는 using 아규먼트를 사용합니다. 제공되지 않을 경우, 장고는 “default” 데이터베이스를 사용합니다.

자동 커밋은 처음에 켜져 있습니다. 전원을 끄면 복구하는 것은 당신의 책임입니다.

 

자동 커밋을 끄면 데이터베이스 어대버의 기본 동작이 표시되며, 장고는 도움이 되지 않습니다. 이 동작은 PEP249에 명시되어 있지만 어댑터 구현이 항상 서로 일관되지는 않습니다. 사용 중인 어댑터의 설명서를 주의 깊게 검토합니다.

 

자동 커밋을 다시 켜기 전에 일반적으로 commit() 또는 롤백을 실행하여 활성 트랜잭션이 없는지 확인해야 합니다.

장고는 atomic block이 활성화되면 atomic을 파괴하기 때문에 자동 커밋을 끄기를 거부합니다.

Transactions

트랜잭션은 데이터베이스 쿼리의 atomic 집합입니다. 프로그램이 충돌하더라도 데이터베이스는 모든 변경사항이 적용되거나 변경사항이 적용되지 않음을 보장합니다.

장고는 트랜잭션을 시작하기 위한 API를 제공하지 않습니다. 트랜잭션을 시작하는 예상 방법은 set_autocommit()을 사용하여 자동커밋을 사용하지 않도록 설정하는 것입니다.

 

트랜잭션을 수행했으면 commit()를 사용하여 이 시점까지 수행한 변경 내용을 적용하거나 rollback()을 사용하여 취소하도록 선택할 수 있습니다. 이러한 함수는 django.db.transaction에 정의되어 있습니다.

 

commit(using=None)

rollback(using=None)

이러한 함수는 데이터베이스 이름이어야 하는 사용 인수를 사용합니다. 제공되지 않을 경우, 장고는 “기본” 데이터베이스를 사용합니다.

atomic() block이 활성화되면 원자성이 손상되므로 장고는 커밋 또는 롤백을 거부합니다.

savepoints

savepoints는 전체 트랜잭션이 아닌 트랜젹선의 일부를 롤백할 수 있는 트랜잭션 내의 마커입니다.

저장점은 SQlite, PostgreSQL, Oracle 및 MySQL(InnoDB 스토리지 엔진 사용시) 백엔드에서 사용할 수 있습니다. 다른 백엔드는 savepoints기능을 제공하지만 실제로는 아무것도 하지 않는 빈 작업입니다.

 

저장점은 Django의 기본 동작인 autocommit을 사용하는 경우 특히 유용하지 않습니다. 그러나 atomic()으로 트랜잭션을 열면 커밋 또는 롤백을 기다리는 일련의 데이터베이스 작업이 생성됩니다. 롤백을 실행하면 전체 트랜잭션이 롤백됩니다. savepoints는 transaction.rollback()에 의해 수행되는 전체 롤백이 아니라 세분화된 롤백을 수행하는 기능을 제공합니다.

 

atomic()데코레이터가 중첩되면 부분 커밋 또는 롤백을 허용하는 savepoints를 만듭니다. 아래에 설명된 함수보다 atomic()을 사용하는 것이 좋습니다. 그러나 여전히 공개 API의 일부이며 더 이상 사용하지 않을 계획입니다.

 

이러한 각 함수는 동작이 적용되는 데이터베이스의 이름이어야하는 using 인수를 취합니다. using 인수가 제공되지 않으면 “기본" 데이터베이스가 사용됩니다.

Savepoints는 django.db.transaction의 세 가지 기능으로 제어됩니다.

 

savepoint(using=None)

새 저장점을 만듭니다. 이것은 “good”상태에 있는 것으로 알려진 트랜잭션의 한 지점을 표시합니다. 저장점 ID(sid)를 반환합니다.

 

savepoint_commit(sid,using=None)

저장 지점 sid를 해제합니다. 저장점이 생성된 이후 수행된 변경사항은 트랜잭션의 일부가 됩니다.

 

savepoint_rollback(sid,using=None)

트랜잭션을 savepoints sid로 롤백합니다.

저장점이 지원되지 않거나 데이터베이스가 자동 커밋 모드에 있는 경우 이러한 기능은 아무 소용이 없습니다.

또한 다음과 같은 유틸리티 기능이 있습니다.

 

clean_savepoints(using=None)

고유한 저장 지점 ID를 생성하는 데 사용되는 카운터를 재설정합니다.

다음 예제에서는 savepoints의 사용을 보여줍니다.

from django.db import transaction

# open a transaction
@transaction.atomic
def viewfunc(request):

    a.save()
    # transaction now contains a.save()

    sid = transaction.savepoint()

    b.save()
    # transaction now contains a.save() and b.save()

    if want_to_keep_b:
        transaction.savepoint_commit(sid)
        # open transaction still contains a.save() and b.save()
    else:
        transaction.savepoint_rollback(sid)
        # open transaction now contains only a.save()

부분 롤백을 수행하여 데이터베이스 오류에서 복구하는 데 savepoints를 사용할 수 있습니다. atomic() block내에서 이 작업을 수행하는 경우 하위 레벨에서 해당 상황을 처리했는지 모르기 때문에 전체 블록이 롤백됩니다.

이를 방지하기 위해 다음 기능을 사용하여 롤백 동작을 제어할 수 있습니다.

 

get_rollback(using=None)

set_rollback(rollback, using=None)

롤백 플래그를 True로 설정하면 가장 안쪽 atomic block을 종료할 때 롤백이 적용됩니다. 이 방법은 예외를 발생시키지 않고 롤백을 트리거하는 데 유용할 수 있습니다.

False로 설정하면 이러한 롤백이 방지됩니다. 이 작업을 수행하기 전에 현재 atomic 블록 내의 정상적인 저장소로 트랜잭션을 롤백했는지 확인하면 되겠습니다. 그렇지 않으면 원자성이 깨지고 데이터 손상이 발생할 수 있습니다.

Database-specific notes

Savepoints in SQLite

SQLite는 savepoints를 지원하지만, sqlite3 모듈의 설계에 결함이 있어 세이브포인트를 거의 사용할 수 없습니다.

자동 커밋을 사용하도록 설정하면 savepoints가 의미가 없습니다. sqlite3가 비활성화되면 savepoint 문 앞에 암묵적으로 커밋됩니다. (실제로 SELECT, INSERT, UPDATE, DELETE, REPLACE 이외의 문 앞에 커밋됩니다. 이 버그에는 두가지 결과가 있습니다.

  • 저장점을 위한 낮은 수준의 API는 트랜잭션 내에서만 사용할 수 있습니다. atomic() block안에서
  • 자동 커밋이 해제된 경우 atomic()을 사용할 수 없습니다.

Transactions in MySQL

MySQL을 사용하는 경우 테이블에서 트랜잭션을 지원하거나 지원하지 않을 수 있습니다. MySQL 버전과 사용중인 테이블 유형에 따라 다릅니다. (테이블 형식에서, 우리는 “InnoDB” 또는 “MyISAM”과 같은 것을 의미합니다) MySQL 트랜잭션 특성은 이 문서의 범위를 벗어나지만 MySQL 사이트에는 MySQL 트랜잭션에 대한 정보가 있습니다.

MySQL 설정이 트랜잭션을 지원하지 않는 경우 Django는 항상 자동 커밋 모드에서 작동합니다. 문이 호출되는 즉시 실행되고 커밋됩니다. MySQL 설정이 트랜잭션을 지원하는 경우 Django는 이 문서에 설명된 대로 트랜잭션을 처리합니다.

PostgreSQL내 예외 처리 SQL 트랜잭션

이 섹션은 자체 트랜잭션 관리를 구현하는 경우에만 관련됩니다. 이 문제는 장고의 기본 모드에서 발생할 수 없으며 atomic()이 자동으로 처리합니다.

트랜잭션 내에서 PostgreSQL에 대한 호출이 있을 때 SQL 커서가 예외(일반적으로 IntegrityError)를 발생시키면 동일한 트랜잭션의 모든 후속 SQL이 “현재 트랜잭션이 중단되었습니다. 트랜잭션 블록이 끝날 때까지 무시됩니다.”라는 오류와 함께 실패합니다. save()의 기본 사용은 PostgreSQL에서 예외를 발생시킬 것 같지 않습니다. SQL에는 고유 필드가 있는 개체 저장, force_insert/force_update 플래그를 사용하여 저장 또는 사용자 지정 SQL 호출과 같은 고급 사용 패턴이 있습니다.

이러한 종류의 오류에서 복구하는 몇 가지 방법이 있습니다.

transaction rollback

첫 번째 옵션은 전체 트랜잭션을 롤백하는 것입니다.

a.save() # Succeeds, but may be undone by transaction rollback
try:
    b.save() # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save() # Succeeds, but a.save() may have been undone

transaction.rollback()을 호출하면 전체 트랜잭션이 롤백됩니다. 커밋되지 않은 데이터베이스 작업은 모두 손실됩니다. 이 예에서 a.save()에 의해 변경된 내용은 해당 작업에서 오류 자체를 발생시키지 않더라도 손실됩니다.

savepoint rollback

savepoints를 사용하여 롤백 범위를 제어할 수 있습니다. 실패할 수 있는 데이터베이스 작업을 수행하기 전에 savepoint를 설정하거나 업데이트할 수 있습니다. 이렇게 하면 작업이 실패할 경우 트랜잭션이 아닌 하나의 문제가 되는 작업을 롤백할 수 있습니다.

a.save() # Succeeds, and never undone by savepoint rollback
sid = transaction.savepoint()
try:
    b.save() # Could throw exception
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save() # Succeeds, and a.save() is never undone

이 예에서는 b.save()가 예외를 발생시키는 경우 a.save()가 실행 취소되지 않습니다.

 

참고 링크

https://docs.djangoproject.com/en/4.1/topics/db/transactions/

 

Database transactions | Django documentation | Django

Django The web framework for perfectionists with deadlines. Overview Download Documentation News Community Code Issues About ♥ Donate

docs.djangoproject.com

 

댓글