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!
fonte
Respostas:
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.
fonte
Existem alguns erros tão graves que o bloco CATCH nunca é inserido. A partir da documentação
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.
fonte
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) .
dan-guzman : Não, o
CATCH
bloco 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 ON
nã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,
SNAPSHOT
ouREAD_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.
fonte
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
fonte
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.
fonte
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:
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 ...
fonte
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.
fonte
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
fonte
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.
fonte