Nota
O acesso a esta página requer autorização. Pode tentar iniciar sessão ou alterar os diretórios.
O acesso a esta página requer autorização. Pode tentar alterar os diretórios.
Este artigo explica como configurar o tratamento de transações e níveis de isolamento para aplicações Django usando o mssql-django backend com o SQL Server.
Comportamento padrão
Por defeito, o Django opera em modo de autocommit. Cada consulta à base de dados executa a sua própria transação e é confirmada imediatamente. Pode alterar este comportamento usando a AUTOCOMMIT definição ou a API de gestão de transações do Django.
Configuração de AUTOCOMMIT
Defina AUTOCOMMIT para False na configuração da sua base de dados para desativar o modo de autocommit:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"AUTOCOMMIT": False,
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
},
},
}
Note
Desativar o autocommit significa que tem de confirmar ou anular explicitamente as transações. A maioria das aplicações Django deixa o autocommit ativado e utiliza transaction.atomic() para operações específicas.
Utilize transaction.atomic()
Envolva as operações transaction.atomic() da base de dados para garantir que são executadas numa única transação:
from django.db import transaction
from myapp.models import Account
def transfer_funds(from_account_id, to_account_id, amount):
with transaction.atomic():
sender = Account.objects.select_for_update().get(pk=from_account_id)
receiver = Account.objects.select_for_update().get(pk=to_account_id)
sender.balance -= amount
receiver.balance += amount
sender.save()
receiver.save()
Se ocorrer alguma exceção dentro de um atomic() bloco, toda a transação é revertida.
Transações aninhadas
O Django suporta blocos aninhados atomic() através dos pontos de restauro do SQL Server:
from django.db import transaction
with transaction.atomic():
# Outer transaction
Product.objects.create(name="Widget A", price=9.99)
try:
with transaction.atomic():
# Inner savepoint
Product.objects.create(name="Widget B", price=14.99)
raise ValueError("Simulated error")
except ValueError:
pass # Inner savepoint is rolled back, outer continues
# Widget A is committed, Widget B is not
Níveis de isolamento de transações
Configure o nível de isolamento da transação usando a isolation_level opção na configuração da sua base de dados:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "READ COMMITTED",
},
},
}
Níveis de isolamento suportados
| Nível de isolamento | Description |
|---|---|
READ UNCOMMITTED |
Permite leituras sujas. Menor isolamento, maior concorrência. |
READ COMMITTED |
SQL Server predefinido. Previne leituras sujas. |
REPEATABLE READ |
Impede leituras sujas e não repetíveis. |
SNAPSHOT |
Utiliza versionamento de linhas para leituras consistentes sem bloqueios. Requer que o isolamento de snapshots ao nível da base de dados esteja ativado. |
SERIALIZABLE |
Isolamento máximo. Previne leituras fantasma. |
Ativar o isolamento SNAPSHOT
Para usar SNAPSHOT isolamento, ative-o primeiro na base de dados:
ALTER DATABASE [<your-database>]
SET ALLOW_SNAPSHOT_ISOLATION ON;
Depois configure-o em settings.py:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"USER": "<your-username>",
"PASSWORD": "<your-password>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "SNAPSHOT",
},
},
}
Use o decorador @transaction.atomic
Aplicar transações a todas as funções de visualização:
from django.db import transaction
from django.http import JsonResponse
@transaction.atomic
def create_order(request):
# All database operations in this view run in a single transaction
order = Order.objects.create(customer_id=request.user.id)
for item in request.POST.getlist("items"):
OrderItem.objects.create(order=order, product_id=item)
return JsonResponse({"order_id": order.pk})
Leitura de dados sem bloqueio (equivalente a NOLOCK)
Uma solicitação frequente é consultar o SQL Server com a sugestão NOLOCK ou o nível de isolamento READ UNCOMMITTED para evitar bloqueios em tabelas com muita atividade. O ORM do Django não gera sugestões de tabela, mas tem duas opções.
Opção 1: Definir READ UNCOMMITTED por ligação
Defina o nível de isolamento como READ UNCOMMITTED num alias dedicado para uma base de dados só de leitura, para que se aplique a todas as consultas nessa ligação:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
},
},
"read_uncommitted": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "READ UNCOMMITTED",
},
},
}
Depois, encaminhar as consultas para o read_uncommitted alias:
# Read with NOLOCK-equivalent behavior
products = Product.objects.using("read_uncommitted").filter(active=True)
# Writes still go through the default connection
Product.objects.create(name="Widget", price=9.99)
Opção 2: Usar SQL bruto com NOLOCK
Para consultas específicas sobre tabelas específicas, utilize SQL em bruto com a diretiva de tabela NOLOCK:
from django.db import connection
with connection.cursor() as cursor:
cursor.execute("SELECT id, name, price FROM myapp_product WITH (NOLOCK) WHERE active = %s", [1])
rows = cursor.fetchall()
Caution
Ambos READ UNCOMMITTED e NOLOCK permitem leituras sujas, o que significa que as consultas podem devolver dados de transações não comprometidas. Use estas técnicas apenas para relatórios ou consultas analíticas onde não é necessária consistência absoluta.
Opção 3: Usar o isolamento SNAPSHOT em alternativa
SNAPSHOT O isolamento proporciona leituras consistentes sem bloqueios e sem leituras sujas. É a alternativa recomendada a NOLOCK para a maioria das cargas de trabalho:
DATABASES = {
"default": {
"ENGINE": "mssql",
"NAME": "<your-database>",
"HOST": "<your-server>",
"PORT": "1433",
"OPTIONS": {
"driver": "ODBC Driver 18 for SQL Server",
"isolation_level": "SNAPSHOT",
},
},
}
SNAPSHOT requer configuração ao nível da base de dados. Veja Ativar isolamento SNAPSHOT.
Bloqueio ao nível da linha com select_for_update()
O select_for_update() do Django é totalmente suportado pelo back-end mssql-django. O SQL Server faz isto usando diretivas de tabela em vez da cláusula FOR UPDATE usada por outras bases de dados.
Utilização básica
from django.db import transaction
with transaction.atomic():
product = Product.objects.select_for_update().get(pk=1)
product.stock -= 1
product.save()
O backend gera: SELECT ... FROM [myapp_product] WITH (ROWLOCK, UPDLOCK) WHERE ...
NOWAIT e SKIP LOCKED
Os nowait parâmetros e skip_locked são ambos suportados:
from django.db import transaction
# Raise DatabaseError immediately if the row is already locked
with transaction.atomic():
product = Product.objects.select_for_update(nowait=True).get(pk=1)
# Skip rows that are locked by other transactions
with transaction.atomic():
available = Product.objects.select_for_update(skip_locked=True).filter(
reserved=False
)[:10]
| Parâmetro | Indicação para tabelas no SQL Server |
|---|---|
| Predefinição | WITH (ROWLOCK, UPDLOCK) |
nowait=True |
WITH (NOWAIT, ROWLOCK, UPDLOCK) |
skip_locked=True |
WITH (ROWLOCK, UPDLOCK, READPAST) |
Note
select_for_update() deve ser usado dentro de um transaction.atomic() bloco. O Django gera um erro se o chamar fora de uma transação.
Diferenças em relação ao PostgreSQL
- O
ofparâmetro (select_for_update(of=(...))) não é suportado. O backend geraNotSupportedErrorse lho passares. - O SQL Server utiliza sugestões ao nível da tabela (
UPDLOCK) em vez de cláusulas ao nível da linhaFOR UPDATE. Em situações de alta contenção, a escalada de bloqueios pode fazer com que mais linhas ou páginas fiquem bloqueadas do que o alvo. Utilize o nível de isolamentoSNAPSHOTse precisar de leituras sem bloqueio a par de escritas com bloqueio.