Devo confirmar ou reverter uma transação de leitura?

95

Tenho uma consulta de leitura que executo dentro de uma transação para que possa especificar o nível de isolamento. Assim que a consulta for concluída, o que devo fazer?

  • Confirme a transação
  • Reverter a transação
  • Não faça nada (o que fará com que a transação seja revertida no final do bloco de uso)

Quais são as implicações de fazer cada um?

using (IDbConnection connection = ConnectionFactory.CreateConnection())
{
    using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
    {
        using (IDbCommand command = connection.CreateCommand())
        {
            command.Transaction = transaction;
            command.CommandText = "SELECT * FROM SomeTable";
            using (IDataReader reader = command.ExecuteReader())
            {
                // Read the results
            }
        }

        // To commit, or not to commit?
    }
}

EDITAR: A questão não é se uma transação deve ser usada ou se existem outras maneiras de definir o nível da transação. A questão é se faz alguma diferença que uma transação que não modifica nada seja confirmada ou revertida. Existe uma diferença de desempenho? Isso afeta outras conexões? Alguma outra diferença?

Stefan Moser
fonte
1
Você provavelmente já sabe sobre isso, mas dado o exemplo que forneceu, você pode obter resultados equivalentes simplesmente executando a consulta: SELECT * FROM SomeTable with NOLOCK
JasonTrue
@Stefan, parece que a maioria de nós se pergunta por que você está se preocupando em realizar uma operação somente leitura. Você pode nos dizer se conhece o NOLOCK e, se conhece, por que não seguiu esse caminho.
StingyJack
Eu sei sobre o NOLOCK, mas este sistema opera em bancos de dados diferentes, bem como no SQL Server, então estou tentando evitar dicas de bloqueio específicas do SQL Server. Esta é uma pergunta mais por curiosidade do que qualquer outra coisa, pois o aplicativo está funcionando bem com o código acima.
Stefan Moser
Ah, nesse caso estou removendo a tag sqlserver, porque isso denota MSSqlServer como o produto de destino.
StingyJack
@StingyJack - Você está certo, eu não deveria ter usado a tag sqlserver.
Stefan Moser

Respostas:

51

Você se compromete. Período. Não há outra alternativa sensata. Se você iniciou uma transação, deve fechá-la. A confirmação libera qualquer bloqueio que você possa ter e é igualmente sensata com os níveis de isolamento ReadUncommitted ou Serializable. Contar com a reversão implícita - embora talvez tecnicamente equivalente - é apenas uma má forma.

Se isso não o convenceu, imagine o próximo cara que insere uma instrução update no meio do seu código e tem que rastrear o rollback implícito que ocorre e remove seus dados.

Mark Brackett
fonte
44
Existe uma alternativa sensata - reversão. Reversão explícita, claro. Se você não pretendia mudar nada, o rollback garante que tudo seja desfeito. Claro, não deveria ter havido nenhuma mudança; rollback garante isso.
Jonathan Leffler
2
DBMS diferentes podem ter semânticas de 'conclusão de transação implícita' diferentes. IBM Informix (e eu acredito que o DB2) faz reversão implícita; por boato, a Oracle faz um commit implícito. Eu prefiro reversão implícita.
Jonathan Leffler
8
Suponha que eu crie uma tabela temporária, preencha-a com ids, junte-a a uma tabela de dados para selecionar os dados que acompanham os ids e, em seguida, exclua a tabela temporária. Na verdade, estou apenas lendo dados e não me importo com o que acontece com a tabela temporária, já que é temporária ... mas de uma perspectiva de desempenho, seria mais caro reverter a transação ou confirmá-la? Qual é o efeito de um commit / rollback quando nada além de tabelas temporárias e operações de leitura estão envolvidas?
Triynko
4
@Triynko - Intuitivamente, acho que ROLLBACK é mais caro. COMMIT é o caso de uso normal e ROLLBACK é o caso excepcional. Mas, exceto academicamente, quem se importa? Tenho certeza de que há 1000 pontos de otimização melhores para seu aplicativo. Se você estiver realmente curioso, pode encontrar o código de tratamento de transação mySQL em bazaar.launchpad.net/~mysql/mysql-server/mysql-6.0/annotate/…
Mark Brackett
3
@Triynko - A única maneira de otimizar é criando um perfil. É uma alteração de código tão simples que não há razão para não criar o perfil de ambos os métodos se você realmente quiser otimizá-los. Certifique-se de nos atualizar com os resultados!
Mark Brackett
28

Se você não mudou nada, então você pode usar um COMMIT ou um ROLLBACK. Qualquer um deles irá liberar todos os bloqueios de leitura que você adquiriu e, como você não fez nenhuma outra alteração, eles serão equivalentes.

Graeme Perrow
fonte
2
Obrigado por me informar que eles são equivalentes. Em minha opinião, isso responde melhor à questão real.
chowey
isso daria a transação inativa se usarmos o commit sem atualizações reais. acabei de enfrentá-lo no meu site ao vivo
Muhammad Omer Aslam
6

Se você iniciar uma transação, a melhor prática é sempre confirmá-la. Se uma exceção for lançada dentro do seu bloco de uso (transação), a transação será automaticamente revertida.

Neil Barnwell
fonte
3

IMHO pode fazer sentido agrupar consultas somente leitura em transações, pois (especialmente em Java) você pode dizer à transação para ser "somente leitura", que por sua vez o driver JDBC pode considerar a otimização da consulta (mas não precisa, então ninguém irá impedi-lo de emitir um INSERTno entanto). Por exemplo, o driver Oracle evitará completamente bloqueios de tabela em consultas em uma transação marcada como somente leitura, o que ganha muito desempenho em aplicativos altamente orientados para leitura.

Oliver Drotbohm
fonte
3

Considere as transações aninhadas .

A maioria dos RDBMSs não oferece suporte a transações aninhadas ou tenta emulá-las de uma forma muito limitada.

Por exemplo, no MS SQL Server, uma reversão em uma transação interna (que não é uma transação real, o MS SQL Server apenas conta os níveis de transação!) Irá reverter tudo o que aconteceu na transação externa (que é a transação real).

Alguns wrappers de banco de dados podem considerar um rollback em uma transação interna como um sinal de que ocorreu um erro e fazer rollback de tudo na transação externa, independentemente se a transação externa foi confirmada ou revertida.

Portanto, um COMMIT é o caminho seguro, quando você não pode descartar que seu componente seja usado por algum módulo de software.

Observe que esta é uma resposta geral à pergunta. O exemplo de código habilmente contorna o problema com uma transação externa abrindo uma nova conexão de banco de dados.

Com relação ao desempenho: dependendo do nível de isolamento, SELECTs podem exigir um grau variável de LOCKs e dados temporários (instantâneos). Isso é limpo quando a transação é fechada. Não importa se isso é feito via COMMIT ou ROLLBACK. Pode haver uma diferença insignificante no tempo gasto da CPU - um COMMIT é provavelmente mais rápido de analisar do que um ROLLBACK (dois caracteres a menos) e outras diferenças menores. Obviamente, isso só é verdade para operações somente leitura!

Totalmente não solicitado: outro programador que possa ler o código pode assumir que um ROLLBACK implica uma condição de erro.

Klaws
fonte
2

Apenas uma observação lateral, mas você também pode escrever esse código assim:

using (IDbConnection connection = ConnectionFactory.CreateConnection())
using (IDbTransaction transaction = connection.BeginTransaction(IsolationLevel.ReadUncommitted))
using (IDbCommand command = connection.CreateCommand())
{
    command.Transaction = transaction;
    command.CommandText = "SELECT * FROM SomeTable";
    using (IDataReader reader = command.ExecuteReader())
    {
        // Do something useful
    }
    // To commit, or not to commit?
}

E se você reestruturar as coisas apenas um pouco, poderá mover o bloco de uso do IDataReader para o topo também.

Joel Coehoorn
fonte
1

Se você colocar o SQL em um procedimento armazenado e adicioná-lo acima da consulta:

set transaction isolation level read uncommitted

então você não tem que passar por nenhum obstáculo no código C #. Definir o nível de isolamento da transação em um procedimento armazenado não faz com que a configuração se aplique a todos os usos futuros dessa conexão (que é algo com que você deve se preocupar com outras configurações, uma vez que as conexões são agrupadas). No final do procedimento armazenado, ele volta para o que quer que a conexão tenha sido inicializada.

Eric Z Beard
fonte
1

ROLLBACK é usado principalmente em caso de erro ou circunstâncias excepcionais, e COMMIT no caso de conclusão bem-sucedida.

Devemos fechar as transações com COMMIT (para sucesso) e ROLLBACK (para falha), mesmo no caso de transações somente leitura onde isso não parece importar. Na verdade, isso é importante, para consistência e segurança futura.

Uma transação somente leitura pode "falhar" logicamente de várias maneiras, por exemplo:

  • uma consulta não retorna exatamente uma linha conforme o esperado
  • um procedimento armazenado levanta uma exceção
  • os dados obtidos são inconsistentes
  • o usuário aborta a transação porque está demorando muito
  • impasse ou tempo limite

Se COMMIT e ROLLBACK forem usados ​​corretamente para uma transação somente leitura, ele continuará a funcionar conforme o esperado se o código de gravação do DB for adicionado em algum ponto, por exemplo, para armazenamento em cache, auditoria ou estatísticas.

ROLLBACK implícito deve ser usado apenas para situações de "erro fatal", quando o aplicativo trava ou sai com um erro irrecuperável, falha de rede, falha de energia, etc.

Sam Watkins
fonte
0

Visto que um READ não muda de estado, eu não faria nada. Executar um commit não fará nada, exceto desperdiçar um ciclo para enviar a solicitação ao banco de dados. Você não executou uma operação que mudou de estado. Da mesma forma para a reversão.

No entanto, você deve limpar seus objetos e fechar suas conexões com o banco de dados. Não fechar suas conexões pode levar a problemas se este código for chamado repetidamente.

Brett McCann
fonte
3
Dependendo do nível de isolamento, uma seleção PODE obter bloqueios que bloquearão outras transações.
Graeme Perrow
A conexão será encerrada no final do bloco de uso - é para isso que existe. Mas é bom lembrar que o tráfego da rede é provavelmente a parte mais lenta da equação.
Joel Coehoorn
1
A transação será confirmada ou revertida de uma maneira ou de outra, portanto, a melhor prática seria sempre emitir uma confirmação se tiver êxito.
Neil Barnwell
0

Se você definir AutoCommit como false, então YES.

Em um experimento com JDBC (driver Postgresql), descobri que, se a consulta de seleção for interrompida (devido ao tempo limite), você não poderá iniciar uma nova consulta de seleção a menos que faça rollback.

Shiv Krishna Jaiswal
fonte
-2

Em seu exemplo de código, onde você tem

  1. // Faça algo útil

    Você está executando uma instrução SQL que altera os dados?

Do contrário, não existe uma transação "Ler" ... Somente as alterações de uma instrução de inserção, atualização e exclusão (instruções que podem alterar dados) estão em uma transação ... O que você está falando são os bloqueios que o SQL O servidor coloca os dados que você está lendo, por causa de OUTRAS transações que afetam esses dados. O nível desses bloqueios depende do Nível de Isolamento do SQL Server.

Mas você não pode Commit, ou ROll Back nada, se sua instrução SQL não mudou nada.

Se você estiver alterando os dados, pode alterar o nível de isolamento sem iniciar explicitamente uma transação ... Cada instrução SQL individual está implicitamente em uma transação. iniciar explicitamente uma transação só é necessário para garantir que 2 ou mais instruções estejam dentro da mesma transação.

Se tudo o que você deseja fazer é definir o nível de isolamento da transação, basta definir o CommandText de um comando como "Definir leitura repetível do nível de isolamento da transação" (ou qualquer nível que desejar), definir CommandType como CommandType.Text e executar o comando. (você pode usar Command.ExecuteNonQuery ())

NOTA: Se você estiver fazendo instruções de leitura MÚLTIPLAS e quiser que todas elas "vejam" o mesmo estado do banco de dados como o primeiro, você precisará definir o nível de isolamento superior Leitura repetível ou serializável ...

Charles Bretana
fonte
// Fazer algo útil não altera nenhum dado, apenas lê. Tudo que quero fazer é especificar o nível de isolamento da consulta.
Stefan Moser
Então, você pode fazer isso sem iniciar explicitamente uma transação do cliente ... Basta executar a string sql "Definir nível de isolamento da transação ReadUncommitted", "... Read Committed", "... RepeatableRead", "... Snapshot" , or "... Serializable" "Set Isolation Level Read Committed"
Charles Bretana
3
As transações ainda são importantes, mesmo se você estiver apenas lendo. Se você quiser fazer várias operações de leitura, fazê-las dentro de uma transação garantirá a consistência. Fazê-los sem um não vai.
MarkR
sim, desculpe, você está certo, pelo menos isso é verdade se o nível de isolamento estiver definido como leitura repetida ou superior.
Charles Bretana
-3

Você precisa impedir que outras pessoas leiam os mesmos dados? Por que usar uma transação?

@Joel - Minha pergunta seria melhor formulada como "Por que usar uma transação em uma consulta de leitura?"

@Stefan - Se você for usar AdHoc SQL e não um procedimento armazenado, basta adicionar o WITH (NOLOCK) após as tabelas na consulta. Dessa forma, você não incorre em sobrecarga (embora mínima) no aplicativo e no banco de dados para uma transação.

SELECT * FROM SomeTable WITH (NOLOCK)

EDIT @ Comentário 3: Como você tinha "sqlserver" nas tags de perguntas, presumi que MSSQLServer era o produto alvo. Agora que esse ponto foi esclarecido, editei as tags para remover a referência específica do produto.

Ainda não tenho certeza de por que você deseja fazer uma transação em uma operação de leitura em primeiro lugar.

StingyJack
fonte
1
Para o nível de isolamento definido de uma só vez. Você pode usar a transação para realmente reduzir a quantidade de bloqueio para a consulta.
Joel Coehoorn
1
Estou usando a transação para poder usar um nível de isolamento inferior e reduzir o bloqueio.
Stefan Moser
@StingyJack - Este código pode ser executado em vários bancos de dados diferentes, portanto, NOLOCK não é uma opção.
Stefan Moser