Gerenciando transações de banco de dados

O Django dá a você poucos caminhos para controlar como as transações de banco de dados são gerenciadas, isso se você estiver usando um banco de dados que suporta transações.

Comportamento padrão de transações com Django

O comportamento padrão do Django é executar com uma transação aberta que ele comita automaticamente quando quaisquer função interna que modifique a base de dados for chamada. Por exemplo, se você chamar model.save() ou model.delete(), a mudança vai ser realizada imediatamente.

Isso parece muito com a configuração auto-commit para a maioria dos bancos de dados. Tão logo você performe uma ação que precisa escrever no banco de dados, o Django produz um comando INSERT/UPDATE/DELETE e então faz um COMMIT. Não há ROLLBACK implícito.

Amarrando transações às requisições HTTP

A forma recomendada de lidar com transações em requisições Web é amarrá-las às fases requisição/resposta através do TransactionMiddleware do Django.

Funciona assim: quando uma requisição é iniciada, o Django começa uma transação. Se a resposta é produzida sem problemas, o Django commita quaisquer transações pendentes. Se a função view produz uma exceção, o Django faz um rollback de quaisquer transações pendentes.

Para ativar esta funcionalidade, apenas adicione o TransactionMiddleware middleware nos eu setting MIDDLEWARE_CLASSES:

MIDDLEWARE_CLASSES = (
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.cache.CacheMiddleware',
    'django.middleware.transaction.TransactionMiddleware',
)

A ordem é bastante importante. O middleware de transação se aplica não somente às funções da view, mas também a todos os módulos middleware que que vem após o mesmo. Então, se você usar o session middleware após o transaction middleware, a criação de sessões será parte da transação.

Uma exceção a isso seria o CacheMiddleware, que nunca é afetado. O cache middleware usa seu próprio cursor de banco de dados (que é mapeado para a sua própria conexão de banco de dados internamente).

Controlando o gerenciamento de transações direto nas views

Para a maioria das pessoas, transações baseadas em requisições funciona maravilhosamente bem. Contudo, se você precisa de um controle mais fino sobre como suas transações são gerenciadas, você pode usar os decoradores do Python para mudar a forma como as transações são lidadas por uma função view em particular.

Note

Mesmo os exemplos abaixo usando funções de view como exemplo, esses decoradores também podem ser aplicados para funções que não view.

django.db.transaction.autocommit

Use o decorador autocommit para mudar o comportamento padrão de commit de uma view, independente da configuração global das transações.

Exemplo:

from django.db import transaction

@transaction.autocommit
def viewfunc(request):
    ....

Dentro de viewfunc(), transações serão commitadas quão logo você chame model.save(), model.delete(), ou qualquer outra função que escreva na base de dados.

django.db.transaction.commit_on_success

Utilize o decorador commit_on_success para usar transações únicas para todo o trabalho feito em uma função:

from django.db import transaction

@transaction.commit_on_success
def viewfunc(request):
    ....

Se a função retorna com sucesso, então o Django irá commitar todo o trabalho feito na função até aquele ponto. Se a função levanta uma exceção, todavia, o Django irá fazer um rollback na transação.

django.db.transaction.commit_manually

Use o decorador commit_manually se você precisa de um controle completo sobre as transações. Ele diz ao Django que você mesmo irá gerenciar a trasação.

Se seu view muda o dado e não executa um commit() ou rollback(), o Django irá lançar uma exceção TransactionManagementError.

Gerenciamento manual de transações parece com isso:

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):
    ...
    # Você pode comitar/retroceder quando e onde quiser.
    transaction.commit()
    ...

    # Mas terá de lembrar de fazê-lo você mesmo!
    try:
        ...
    except:
        transaction.rollback()
    else:
        transaction.commit()

Uma nota importante para usuários de versões anteriores do Django:

Os métodos de banco de dados connection.commit() e connection.rollback() (chamados db.commit() e db.rollback na versão 0.91 e anteriores) não existem mais. Eles foram substituídos por transation.commit() e transation.rollback().

Como desativar globalmente o gerenciamentod e transações

Controladores obsecados, podem disabilitar totalmente todo gerenciamento de transações Control freaks can totally disable all transaction management by setting DISABLE_TRANSACTION_MANAGEMENT to True in the Django settings file.

Se você fizer isso, o Django não irá prover qualquer gerenciamento de transação automático qualquer. O middleware não irá implicitamente comitar as transações, e você precisará gerenciar os rollbacks. Isto requer que você mesmo comita as alterações feitas pelo middleware e algo a mais.

Deste modo, é melhor usar isto em situações onde você precisa rodar o seu próprio middleware de controle de transações ou fazer algo realmente estranho. Em quase todas as situações, você se dará melhor usando o comportamento padrão, ou o middleware, e somente modificar as funções selecionadas caso precise.

Savepoints

Um savepoint é um marcador dentro de uma trasação que habilida você a retornar parte de uma transação, ao invés de toda ela. Os savepoints estão disponíveis para backends PostgreSQL 8 e Oracle. Outros backends proverão funções savepoint, mas eles são operações vazias - eles na verdade não fazem nada.

Os savepoints não são usuais especialmente se você estiver usando o comportamento padrão autocommit do Django. Entretanto, se você estiver usando commit_on_success ou commit_manually, cada transação aberta construirá uma série de operações de banco de dados, que aguardarão um commit ou um rollback. Se você emitir um rollback, a transação inteira é retrocedida. Os savepoints fornecem um habilidade de executar rollbacks com granularidade fina, ao invés de um rollback completo que poderia ser executada pelo transaction.rollback().

Os savepoints são controlados por três métodos do objeto transação:

transaction.savepoint()

Cria um novo savepoint. Este marca um ponto na transação que é tido como um “bom” estado.

Retorna o ID do savepoint (sid).

transaction.savepoint_commit(sid)

Atualiza o savepoint para incluir quaisquer operações que tenham sido executadas desde que o savepoint foi criado, ou desde o último commit.

transaction.savepoint_rollback(sid)

Retrocede a transação para o último ponto em que o savepoint foi commitado.

O exemplo a seguir demonstra o uso de savepoints:

from django.db import transaction

@transaction.commit_manually
def viewfunc(request):

  a.save()
  # abre a transação agora contentdo a.save()
  sid = transaction.savepoint()

  b.save()
  # abre a transação agora contendo a.save() e b.save()

  if want_to_keep_b:
      transaction.savepoint_commit(sid)
      # abre a transação que ainda contém a.save() e b.save()
  else:
      transaction.savepoint_rollback(sid)
      # abre a transação agora contendo somente a.save()

  transaction.commit()

Transações no MySQL

Se você estiver usando o MySQL, suas tabelas podem ou não suportar transações; isto depende da versão do MySQL e do tipo de tabelas que você está usando. (Para “tipo de tabela,” significa algo como “InnoDB” ou “MyISAM”.) As peculiaridades de transações no MySQL são fora do escopo do artigo, mas o site do MySQL tem informações sobre as transações no MySQL.

Se sua instalação do MySQL não suporta transações, então o Django irá funcionar no modo auto-comit: As consultas serão executadas e comitadas logo que forem chamadas. Se sua instalação do MySQL suporta transações, o Django irá manipular as transações como explicado neste documento.

Manipulando exceções em transações do PostgreSQL

Quando uma chamada para um cursor do PostgreSQL lança uma exceção (tipicamente IntegrityError), todo o SQL subsequente na mesma transação irá falhar com o o erro “current transation is aborted, queries ignored until end of transation block”, ou seja, “a transação atual foi abortada, consultas ignoradas até o final do bloco da transação”. Embora seja improvável que o uso de um simples save() gere uma exceção no POstgreSQL, há outros usos mais avançados que podem, como o de salvar objetos com campos únicos, salvando usando o flag force_insert/force_update, ou invocando uma SQL customizada.

Há várias maneiras de se deparar com este tipo de erro.

Rollback de transações

A primeira opção é retroceder toda a transação. Por exemplo:

a.save() # Sucesso, mas pode ser desfeita com rollback de transação
try:
    b.save() # Could throw exception
except IntegrityError:
    transaction.rollback()
c.save() # sucesso, mas a.save() pode ter sido desfeita

Chamando transation.rollback() retrocederá toda a transação. Qualquer operação de banco de dados não comitada será perdida. Neste exemplo, as mudanças feitas pelo a.save() poderiam ser perdidas, mesmo que a operação não gere um erro por si só.

Rollback de savepoints

Se você está usando o PostgreSQL 8 ou superior, você pode usar savepoints para controlar a extensão do rollback. Antes de realizar uma operação de banco de dados que possa falhar, você pode setar ou atualizar o savepoint; dessa forma, se a operação falhar, você pode retroceder uma única operação, ao invés de toda transação. Por exemplo:

a.save() # Sucesso, e nunca será desfeita pelo rollback do savepoint
try:
    sid = transaction.savepoint()
    b.save() # Poderia lançar uma exceção
    transaction.savepoint_commit(sid)
except IntegrityError:
    transaction.savepoint_rollback(sid)
c.save() # Sucesso, e a.save() não será desfeita

Neste exemplo, a.save() não será desfeito no caso de b.save() lançar uma exceção.