Solicitado a não usar transações e a usar uma solução alternativa para simular uma

43

Desenvolvo o T-SQL há vários anos e estou sempre aprofundando, continuando a aprender tudo o que posso sobre todos os aspectos da linguagem. Recentemente, comecei a trabalhar em uma nova empresa e recebi o que acho uma sugestão estranha sobre transações. Nunca os use. Em vez disso, use uma solução alternativa que simule uma transação. Isso vem do nosso DBA, que trabalha em um banco de dados com muitas transações e, posteriormente, muito bloqueio. O banco de dados em que trabalho principalmente não sofre com esse problema e vejo que as transações foram usadas no passado.

Eu entendo que o bloqueio é esperado nas transações, pois é da natureza delas e se você pode fugir sem usar uma, faça isso de qualquer maneira. Mas tenho muitas ocasiões em que cada instrução DEVE executar com sucesso. Se alguém falha, todos devem falhar em se comprometer.

Sempre mantive o escopo de minhas transações o mais restrito possível, sempre usado em conjunto com SET XACT_ABORT ON e sempre dentro de um TRY / CATCH.

Exemplo:

CREATE SCHEMA someschema;
GO


CREATE TABLE someschema.tableA
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColA VARCHAR(10) NOT NULL
);
GO

CREATE TABLE someschema.tableB
(id   INT NOT NULL IDENTITY(1, 1) PRIMARY KEY, 
 ColB VARCHAR(10) NOT NULL
); 
GO


CREATE PROCEDURE someschema.ProcedureName @ColA VARCHAR(10), 
                                          @ColB VARCHAR(10)
AS
SET NOCOUNT, XACT_ABORT ON;
BEGIN
BEGIN TRY
    BEGIN TRANSACTION;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);

--Implement error
    SELECT 1/0 

    COMMIT TRANSACTION;
END TRY
BEGIN CATCH
    IF @@trancount > 0
    BEGIN
        ROLLBACK TRANSACTION;
    END;
    THROW;
    RETURN;
END CATCH;
END;
GO

Aqui está o que eles sugeriram que eu faça.

GO



CREATE PROCEDURE someschema.ProcedureNameNoTransaction @ColA VARCHAR(10), 
                                                       @ColB VARCHAR(10)
AS
SET NOCOUNT ON;
BEGIN
BEGIN TRY
    DECLARE @tableAid INT;
    DECLARE @tableBid INT;

    INSERT INTO someschema.tableA(ColA)
    VALUES(@ColA);
    SET @tableAid = SCOPE_IDENTITY();

    INSERT INTO someschema.tableB(ColB)
    VALUES(@ColB);
    SET @tableBid = SCOPE_IDENTITY();

--Implement error
    SELECT 1/0 

END TRY
BEGIN CATCH
    DELETE FROM someschema.tableA
    WHERE id = @tableAid;

    DELETE FROM someschema.tableB
    WHERE id = @tableBid;

    THROW;

    RETURN;
END CATCH;
END;
GO

Minha pergunta para a comunidade é a seguinte. Isso faz sentido como uma solução alternativa viável para transações?

Minha opinião sobre o que sei sobre transações e o que a solução está propondo é que não, essa não é uma solução viável e apresenta muitos pontos de falha.

Na solução sugerida, vejo quatro transações implícitas ocorrendo. As duas inserções na tentativa e depois mais duas transações para as exclusões na captura. Ele "desfaz" as inserções, mas sem reverter nada, para que nada seja revertido.

Este é um exemplo muito básico para demonstrar o conceito que eles estão sugerindo. Alguns dos procedimentos armazenados reais nos quais eu venho fazendo isso os tornam exaustivamente longos e difíceis de gerenciar porque "reverter" vários conjuntos de resultados versus dois valores de parâmetro neste exemplo se torna bastante complicado, como você pode imaginar. Como "retroceder" está sendo feito manualmente agora, a oportunidade de perder algo porque é real.

Outro problema que acho que existe é para tempos limite ou conexões interrompidas. Isso ainda é revertido? Este é o meu entendimento de por que SET XACT_ABORT ON deve ser usado para que, nesses casos, a transação seja revertida.

Agradecemos desde já o seu feedback!

Forrest
fonte
4
Os comentários que não atendem ao seu objetivo declarado foram excluídos ou movidos para a resposta do Wiki da Comunidade.
Paul White

Respostas:

61

Você não pode não usar transações no SQL Server (e provavelmente qualquer outro RDBMS adequada). Na ausência de limites de transação explícitos ( begin transaction... commit), cada instrução SQL inicia uma nova transação, que é implicitamente confirmada (ou revertida) após a conclusão da instrução (ou falha).

A simulação de transação sugerida pela pessoa que se apresenta como seu "DBA" falha em garantir três das quatro propriedades necessárias do processamento da transação, porque aborda apenas erros "suaves" e não é capaz de lidar com erros "difíceis", como desconexões de rede, falta de energia, falhas de disco, etc.

  • Atomicidade: falha. Se ocorrer um erro "rígido" em algum lugar no meio da sua pseudo-transação, a alteração será não atômica.

  • Consistência: falha. Segue-se do exposto acima que seus dados estarão em um estado inconsistente após um erro "rígido".

  • Isolamento: falha. É possível que uma pseudo-transação simultânea altere alguns dos dados modificados pela sua pseudo-transação antes que a sua seja concluída.

  • Durabilidade: sucesso. As alterações feitas serão duráveis, o servidor de banco de dados garantirá isso; essa é a única coisa que a abordagem do seu colega não pode estragar.

Os bloqueios são um método amplamente utilizado e empiricamente bem-sucedido para garantir a ACIDIDADE das transações em todos os tipos ou RDBMSes (este site é um exemplo). Acho muito improvável que um DBA aleatório possa encontrar uma solução melhor para o problema de concorrência do que centenas, possivelmente milhares de cientistas e engenheiros de computação que construíram alguns sistemas de banco de dados interessantes nos últimos 50 anos, o que? 60 anos? (Sei que isso é um tanto falacioso como um argumento de "apelo à autoridade", mas continuarei com ele de qualquer maneira.)

Em conclusão, ignore o conselho do seu "DBA", se puder, lute contra ele se tiver espírito e volte aqui com problemas de concorrência específicos, se eles surgirem.

mustaccio
fonte
14

Existem alguns erros tão graves que o bloco CATCH nunca é inserido. A partir da documentação

Erros com gravidade igual ou superior a 20 que interrompem o processamento da tarefa do Mecanismo de Banco de Dados do SQL Server para a sessão. Se ocorrer um erro com gravidade igual ou superior a 20 e a conexão com o banco de dados não for interrompida, TRY ... CATCH tratará do erro.

Atenções, como solicitações de interrupção do cliente ou conexões de clientes interrompidas.

Quando a sessão é encerrada por um administrador do sistema usando a instrução KILL.

...

Erros de compilação, como erros de sintaxe, que impedem a execução de um lote.

Erros que ocorrem ... por causa da resolução de nome adiada.

Muitos desses são fáceis de produzir através do SQL dinâmico. As instruções de desfazer mostradas não protegerão seus dados de tais erros.

Michael Green
fonte
2
Certo - e se nada mais, o cliente que morreu enquanto executava o código constituiria um erro "tão grave que o bloco CATCH nunca é inserido". Não importa o quanto você confie no software (não apenas em seu próprio código, mas em TODAS as partes de TODAS as pilhas de software envolvidas), sempre há a possibilidade de uma falha de hardware (novamente, potencialmente em qualquer lugar da cadeia), impedindo que você fique frio a qualquer momento. . Manter isso em mente é uma boa defesa contra o tipo de pensamento que leva a esse tipo de "solução alternativa".
dgould 13/09
2
Além disso, você pode ser uma vítima de impasse. Seus blocos CATCH são executados, mas lançados, se eles tentarem gravar no banco de dados.
Joshua
10

i-one : A solução alternativa sugerida possibilita (pelo menos) a violação de "A" do ACID . Por exemplo, se o SP estiver sendo executado por um cliente remoto e houver interrupções na conexão, poderá ocorrer "confirmação" / "reversão" parcial, pois o servidor poderá encerrar a sessão entre duas inserções / exclusões (e interromper a execução do SP antes que ele termine) .

Isso faz sentido como uma solução alternativa viável para transações?

dan-guzman : Não, oCATCHbloco nunca é executado no caso de um tempo limite de consulta porque a API do cliente cancelou o lote. Sem uma transação,SET XACT_ABORT ONnão é possível reverter nada além da instrução atual.

tibor-karaszi : você tem 4 transações, o que significa mais registro no arquivo de log de transações. Lembre-se de que cada transação requer uma gravação síncrona dos registros de log até aquele ponto, ou seja, você também obtém desempenho pior nesse aspecto ao usar muitas transações.

rbarryyoung : se eles estão recebendo muitos bloqueios, eles precisam corrigir o design dos dados, racionalizar a ordem de acesso à tabela ou usar um nível de isolamento mais apropriado. Eles estão assumindo que seus problemas (e falta de compreensão) se tornarão seu problema. A evidência de milhões de outros bancos de dados é que não.

Além disso, o que eles estão tentando implementar manualmente é efetivamente uma simultaneidade otimista dos pobres. O que eles devem fazer é usar algumas das melhores simultâneas otimistas do mundo, já incorporadas ao SQL Server. Isso vai para o ponto de isolamento acima. Com toda a probabilidade, eles precisam mudar de qualquer nível de isolamento de simultaneidade pessimista que estejam usando atualmente para um dos níveis otimistas de isolamento de simultaneidade, SNAPSHOTou READ_COMMITTED_SNAPSHOT. Eles efetivamente farão o mesmo que o código manual, exceto que o farão corretamente.

ross-presser : Se você tem processos de execução extremamente longos - como se algo acontecesse hoje e na próxima semana algo tiver que acompanhar, e se as coisas da próxima semana falharem, então o dia de hoje falhar retroativamente - você pode procurar sagas . Estritamente falando, isso está fora do banco de dados, pois requer um barramento de serviço.

user126897
fonte
5

O código da má ideia só será mais caro para corrigir a linha.

Se houver problemas de bloqueio usando transações explícitas (reversão / confirmação), aponte seu DBA para a Internet para obter ótimas idéias para resolver os problemas.

Aqui está uma maneira de ajudar a aliviar o bloqueio: https://www.sqlservercentral.com/articles/using-indexes-to-reduce-blocking-in-concurrent-transactions

Os índices reduzem o número de pesquisas que devem ocorrer em uma tabela / página para encontrar uma linha / conjunto de linhas. Eles geralmente são vistos como um método para reduzir o tempo de execução de consultas SELECT * e com razão também. Eles não são considerados adequados para tabelas envolvidas em um grande número de ATUALIZAÇÕES. De fato, INDEXES são desfavoráveis ​​nesses casos, pois aumentam o tempo necessário para concluir as consultas UPDATE.

Mas nem sempre é esse o caso. Aprofundando um pouco a execução de uma instrução UPDATE, descobrimos que ela também envolve a execução de uma instrução SELECT primeiro. Este é um cenário especial e frequentemente visto em que as consultas atualizam conjuntos de linhas mutuamente exclusivos. ÍNDICES aqui podem levar a um aumento significativo no desempenho do mecanismo de banco de dados, contrariamente à crença popular.

user238855
fonte
4

A estratégia de transação falsa é perigosa porque permite problemas de simultaneidade que as transações evitam especificamente. Considere que no segundo exemplo, qualquer um dos dados pode ser alterado entre instruções.

As exclusões de transações falsas não são GARANTIDAS para execução ou êxito. Se o servidor do banco de dados desligar durante a transação falsa, alguns efeitos, mas não todos, permanecerão. Também não é garantido que eles sejam bem-sucedidos da maneira que uma reversão de transação é.

Essa estratégia pode funcionar com inserções, mas definitivamente não funcionaria com atualizações ou exclusões (sem instruções SQL da máquina do tempo).

Se a simultânea transação estrita está causando o bloqueio, há muitas soluções, mesmo as que reduzem o nível de proteção ... essa é a maneira correta de resolver o problema.

Seu DBA está oferecendo uma solução que pode funcionar bem se houver apenas um usuário do banco de dados, mas é absolutamente imprópria para qualquer tipo de uso sério.

Baileys
fonte
4

Este não é um problema de programação, é um problema interpessoal / de falta de comunicação. Muito provavelmente o seu "DBA" está preocupado com bloqueios, não com transações.

As outras respostas já explicam por que você precisa usar transações ... quero dizer, é o que o RDBMS faz, sem transações usadas corretamente, não há integridade dos dados; portanto, vou me concentrar em como resolver o problema real, que é: descubra por que seu "DBA" desenvolveu uma alergia a transações e o convence a mudar de idéia.

Eu acho que esse cara está confundindo "um cenário específico em que o código incorreto resultou em um desempenho terrível" com "todas as transações são ruins". Eu não esperaria que um DBA competente cometesse esse erro, então isso é realmente estranho. Talvez ele tenha tido uma péssima experiência com algum código terrível?

Considere um cenário como este:

BEGIN
UPDATE or DELETE some row, which takes locks it
...do something that takes a while
...perform other queries
COMMIT

Esse estilo de uso de transação mantém um bloqueio (ou vários bloqueios), o que significa que outras transações que atingem as mesmas linhas terão que esperar. Se os bloqueios forem retidos por um longo período de tempo, e especialmente se muitas outras transações quiserem bloquear as mesmas linhas, isso poderá prejudicar o desempenho.

O que você pode fazer é perguntar a ele por que ele tem essa ideia curiosamente errada de não usar transações, que tipos de consultas eram problemáticos etc. etc. desempenho, tranquilizá-lo, etc.

O que ele está dizendo é "não toque na chave de fenda!" então o código que você postou na sua pergunta está basicamente usando um martelo para acionar um parafuso. Uma opção muito melhor é convencê-lo de que você sabe usar uma chave de fenda ...

Eu posso pensar em vários exemplos ... bem, eles estavam no MySQL, mas isso deveria funcionar também.

Houve um fórum em que o índice de texto completo demorou um pouco para atualizar. Quando um usuário enviava uma postagem, a transação atualizava a tabela de tópicos para aumentar a contagem e a data da última postagem (bloqueando assim a linha do tópico), depois inseria a postagem e a transação retinha o bloqueio até que o índice de texto completo terminasse de atualizar e o COMMIT foi feito.

Como isso funcionava em um depósito de ferrugem com pouca memória RAM, a atualização do referido índice de texto completo geralmente resultava em vários segundos de IO aleatória intensa na única unidade de rotação lenta da caixa.

O problema era que as pessoas que clicaram no tópico fizeram com que uma consulta aumentasse a contagem de visualizações no tópico, o que também exigia um bloqueio na linha do tópico. Assim, ninguém podia ver o tópico enquanto o índice de texto completo estava sendo atualizado. Quero dizer, a linha pode ser lida, mas atualizá-la trava.

Pior ainda, a postagem atualizaria a contagem de postagens na tabela de fóruns pai e também manteria o bloqueio enquanto o índice de texto completo estava sendo atualizado ... o que congelou o fórum inteiro por alguns segundos e causou um monte de solicitações na pilha do servidor da Web .

A solução foi pegar os bloqueios na ordem correta: COMEÇA, insira a postagem e atualize o índice de texto completo sem fazer bloqueios; em seguida, atualize rapidamente as linhas de tópico / fórum com a contagem de postagens e a data da última postagem e COMMIT. Isso resolveu completamente o problema. Estava apenas movendo algumas consultas, realmente simples.

Nesse caso, as transações não eram o problema ... Estava adquirindo um bloqueio desnecessário antes de uma operação demorada. Outros exemplos de coisas a serem evitadas mantendo um bloqueio em uma transação: aguardando a entrada do usuário, acessando muitos dados não armazenados em cache de unidades rotativas lentas, E / S de rede, etc.

É claro que, às vezes, você não tem escolha e precisa fazer um longo processamento enquanto mantém bloqueios pesados. Existem truques para isso (operar em uma cópia dos dados, etc.), mas muitas vezes o gargalo de desempenho vem de um bloqueio que não foi adquirido intencionalmente, e simplesmente reordenar as consultas resolve o problema. Melhor ainda, é estar ciente dos bloqueios efetuados ao escrever as consultas ...

Não vou repetir as outras respostas, mas realmente ... use transações. Seu problema está convencendo o seu "DBA", não contornando o recurso mais importante de um banco de dados ...

peufeu
fonte
3

TLDR: Use o nível de isolamento adequado .

Como você notou corretamente, a abordagem sem transações e com a recuperação "manual" pode ser muito complexa. A alta complexidade normalmente significa muito mais tempo para implementá-lo e muito mais tempo para corrigir erros (porque a complexidade leva a mais erros na implementação). Isso significa que essa abordagem pode custar muito mais ao seu cliente.

A principal preocupação do seu colega "dba" é o desempenho. Uma das maneiras de aprimorá-lo é usar o nível de isolamento adequado. Suponha que você tenha um procedimento que forneça algum tipo de dados gerais para o usuário. Esse procedimento não precisa necessariamente usar o nível de isolamento SERIALIZABLE. Em muitos casos, LEIA NÃO COMPROMISSO pode ser suficiente. Isso significa que esse procedimento não será bloqueado por sua transação que cria ou modifica alguns dados.

Eu sugiro que você revise todas as funções / procedimentos existentes no seu banco de dados, avalie o nível razoável de isolamento de cada um, explique os benefícios de desempenho para o seu cliente. Em seguida, ajuste essas funções / procedimentos de acordo.

mentallurg
fonte
2

Você também pode optar por usar as tabelas OLTP na memória. É claro que eles ainda usam transações, mas não há bloqueio envolvido.
Em vez de bloquear, todas as operações serão bem-sucedidas, mas durante a fase de consolidação, o mecanismo verificará conflitos de transação e uma das confirmações poderá falhar. A Microsoft usa o termo "bloqueio otimista".
Se o problema de dimensionamento for causado por conflito entre duas operações de gravação, como duas transações simultâneas tentando atualizar a mesma linha, o OLTP na memória permitirá que uma transação seja bem-sucedida e falhe na outra transação. A transação com falha deve ser reenviada de forma explícita ou implícita, tentando novamente a transação.
Mais em: OLTP na memória

Piotr
fonte
-5

Existe uma maneira de contornar o uso de transações em uma extensão limitada, ou seja, alterando seu modelo de dados para ser mais orientado a objetos. Portanto, em vez de armazenar, por exemplo, dados demográficos sobre uma pessoa em várias tabelas e relacioná-los entre si e exigir transações, você pode ter um único documento JSON que armazena tudo o que sabe sobre essa pessoa em um único campo. É claro que trabalhar até o ponto em que o domínio se estende é outro desafio de design, melhor feito por desenvolvedores e não por DBAs.

user2127
fonte