Por que as pessoas odeiam tanto os cursores SQL?

Eu posso entender querendo evitar ter que usar um cursor devido à sobrecarga e à inconveniência, mas parece que há uma séria mania de fobia por cursor acontecendo onde as pessoas estão indo muito longe para evitar ter que usar uma.

Por exemplo, uma pergunta perguntou como fazer algo obviamente trivial com um cursor e a resposta aceita proposta usando uma consulta recursiva de expressão de tabela comum (CTE) com uma function personalizada recursiva, embora isso limite o número de linhas que poderiam ser processadas para 32 (devido ao limite de chamadas da function recursiva no sql server). Isso me parece uma solução terrível para a longevidade do sistema, para não mencionar um tremendo esforço apenas para evitar o uso de um simples cursor.

Qual é a razão para este nível de ódio insano? Alguma “autoridade notável” emitiu uma fatwa contra os cursores? Algum mal indescritível se esconde no coração dos cursores que corrompe a moral das crianças ou algo assim?

Pergunta do Wiki, mais interessada na resposta do que no representante.

Informação relacionada:

Cursores de Encaminhamento Rápido do SQL Server

EDIT: deixe-me ser mais preciso: eu entendo que os cursores não devem ser usados ​​em vez de operações relacionais normais ; isso é um acéfalo. O que eu não entendo é que as pessoas vão sair do seu caminho para evitar cursores como eles têm piolhos ou algo assim, mesmo quando um cursor é uma solução mais simples e / ou mais eficiente. É o ódio irracional que me deixa perplexo, não as óbvias eficiências técnicas.

O “overhead” com cursores é apenas parte da API. Cursores são como partes do RDBMS funcionam sob o capô. Muitas vezes, CREATE TABLE e INSERT possuem instruções SELECT , e a implementação é a implementação óbvia do cursor interno.

O uso de “operadores baseados em conjunto” de nível superior agrupa os resultados do cursor em um único conjunto de resultados, o que significa menos retorno e retorno da API.

Cursores antecedem linguagens modernas que fornecem collections de primeira class. Old C, COBOL, Fortran, etc., tinham que processar as linhas uma de cada vez, porque não havia noção de “coleção” que pudesse ser usada amplamente. Java, C #, Python, etc., possuem estruturas de lista de primeira class para conter conjuntos de resultados.

O problema lento

Em alguns círculos, as junções relacionais são um mistério, e as pessoas escrevem cursores nesteds em vez de uma simples junit. Eu vi operações de loop nested verdadeiramente épicas escritas como muitos e muitos cursores. Derrotando uma otimização de RDBMS. E correndo muito devagar.

O SQL simples reescreve para replace loops de cursor nesteds por junções e um único loop de cursor plano pode fazer com que os programas sejam executados na centésima vez. [Eles pensaram que eu era o deus da otimização. Tudo o que fiz foi replace loops nesteds por junções. Ainda usei cursores.]

Essa confusão geralmente leva a uma acusação de cursores. No entanto, não é o cursor, é o uso incorreto do cursor que é o problema.

A questão do tamanho

Para conjuntos de resultados realmente épicos (ou seja, despejar uma tabela em um arquivo), os cursores são essenciais. As operações baseadas em conjunto não podem materializar conjuntos de resultados realmente grandes como uma única coleção na memory.

Alternativas

Eu tento usar uma camada ORM, tanto quanto possível. Mas isso tem dois propósitos. Primeiro, os cursores são gerenciados pelo componente ORM. Em segundo lugar, o SQL é separado do aplicativo em um arquivo de configuração. Não é que os cursores sejam ruins. É que codificar todas as aberturas, fechamentos e buscas não é uma programação com valor agregado.

Cursores fazem com que as pessoas apliquem excessivamente uma mentalidade processual a um ambiente baseado em conjuntos.

E eles são lentos !!!

De SQLTeam :

Por favor, note que os cursores são a maneira MAIS LENTA de acessar dados dentro do SQL Server. O deve ser usado somente quando você realmente precisa acessar uma linha de cada vez. A única razão que posso pensar para isso é chamar um procedimento armazenado em cada linha. No artigo Cursor Performance , descobri que os cursores são mais de trinta vezes mais lentos do que as alternativas baseadas em conjunto .

Há uma resposta acima que diz “cursores são a maneira MAIS LENTA de acessar dados dentro do SQL Server … os cursores são mais de trinta vezes mais lentos do que as alternativas baseadas em configurações”.

Essa afirmação pode ser verdadeira em muitas circunstâncias, mas como uma declaração geral é problemática. Por exemplo, fiz um bom uso de cursores em situações em que desejo executar uma operação de atualização ou exclusão que afeta muitas linhas de uma tabela grande que está recebendo leituras de produção constantes. Executar um procedimento armazenado que faz essas atualizações uma linha por vez acaba sendo mais rápido do que as operações baseadas em conjunto, porque a operação baseada em conjunto entra em conflito com a operação de leitura e acaba causando problemas horríveis de bloqueio (e pode matar o sistema de produção completamente). em casos extremos).

Na ausência de outra atividade de database, as operações baseadas em conjunto são universalmente mais rápidas. Nos sistemas de produção, isso depende.

Os cursores tendem a ser usados ​​iniciando desenvolvedores de SQL em locais onde as operações baseadas em conjuntos seriam melhores. Particularmente, quando as pessoas aprendem SQL depois de aprender uma linguagem de programação tradicional, a mentalidade de “iterar esses registros” tende a levar as pessoas a usar os cursores inadequadamente.

Os livros de SQL mais sérios incluem um capítulo que recomenda o uso de cursores; os bem escritos deixam claro que os cursores têm o seu lugar, mas não devem ser usados ​​para operações baseadas em conjuntos.

Existem obviamente situações em que os cursores são a escolha correta, ou pelo menos uma escolha correta.

O otimizador geralmente não pode usar a álgebra relacional para transformar o problema quando um método de cursor é usado. Muitas vezes, um cursor é uma ótima maneira de resolver um problema, mas o SQL é uma linguagem declarativa, e há muitas informações no database, desde restrições a statistics e índices, o que significa que o otimizador tem muitas opções para resolver o problema. problema, enquanto um cursor praticamente direciona explicitamente a solução.

Nos cursores do Oracle PL / SQL não resultarão bloqueios de tabela e é possível usar coleta em massa / busca em massa.

No Oracle 10, o cursor implícito frequentemente usado

  for x in (select ....) loop --do something end loop; 

obtém implicitamente 100 linhas de cada vez. A coleta explícita em massa / coleta em massa também é possível.

No entanto, os cursores PL / SQL são um último recurso, use-os quando não for possível resolver um problema com o SQL baseado em conjunto.

Outro motivo é a paralelização, é mais fácil para o database paralelizar instruções baseadas em conjuntos grandes do que código imperativo linha por linha. É a mesma razão pela qual a functional programming se torna cada vez mais popular (Haskell, F #, Lisp, LINQ C #, MapReduce …), functional programming facilita a paralelização. O número de CPUs por computador está aumentando, então a paralelização se torna mais e mais um problema.

As respostas acima não enfatizaram suficientemente a importância do bloqueio. Eu não sou um grande fã de cursores porque eles geralmente resultam em bloqueios de nível de tabela.

Em geral, como em um database relacional, o desempenho do código usando cursores é uma ordem de magnitude pior que as operações baseadas em conjunto.

Por que vale a pena eu li que o “um” lugar que um cursor executará sua contraparte baseada em conjunto está em um total em execução. Em uma tabela pequena, a velocidade de sumr as linhas sobre a ordem por colunas favorece a operação baseada em conjunto, mas à medida que a tabela aumenta o tamanho da linha, o cursor se tornará mais rápido, pois pode simplesmente transportar o valor total em execução para a próxima passagem. loop. Agora, onde você deve fazer um total de execução é um argumento diferente …

Fora dos problemas de desempenho (não), acho que a maior falha dos cursores é que eles são difíceis de depurar. Especialmente comparado ao código na maioria dos aplicativos clientes, onde a debugging tende a ser relativamente fácil e os resources de linguagem tendem a ser muito mais fáceis. Na verdade, afirmo que quase tudo o que se está fazendo em SQL com um cursor provavelmente deveria estar acontecendo no aplicativo cliente em primeiro lugar.

Você pode postar esse exemplo de cursor ou link para a pergunta? Há provavelmente uma maneira ainda melhor do que um CTE recursivo.

Além de outros comentários, os cursores, quando usados ​​incorretamente (o que é frequente), causam bloqueios de página / linha desnecessários.

Você provavelmente poderia ter concluído sua pergunta após o segundo parágrafo, em vez de chamar as pessoas de “loucas” simplesmente porque elas têm um ponto de vista diferente do seu e tentar enganar profissionais que podem ter uma boa razão para se sentirem assim.

Quanto à sua pergunta, embora existam certamente situações em que um cursor pode ser chamado, na minha experiência, os desenvolvedores decidem que um cursor “deve” ser usado mais frequentemente do que realmente é o caso. A chance de alguém errar no uso excessivo de cursores vs. não usá-los quando deveriam é MUITO mais alta na minha opinião.

Basicamente 2 blocos de código que fazem a mesma coisa. talvez seja um exemplo um pouco estranho, mas isso prova o ponto. SQL Server 2005:

 SELECT * INTO #temp FROM master..spt_values DECLARE @startTime DATETIME BEGIN TRAN SELECT @startTime = GETDATE() UPDATE #temp SET number = 0 select DATEDIFF(ms, @startTime, GETDATE()) ROLLBACK BEGIN TRAN DECLARE @name VARCHAR DECLARE tempCursor CURSOR FOR SELECT name FROM #temp OPEN tempCursor FETCH NEXT FROM tempCursor INTO @name SELECT @startTime = GETDATE() WHILE @@FETCH_STATUS = 0 BEGIN UPDATE #temp SET number = 0 WHERE NAME = @name FETCH NEXT FROM tempCursor INTO @name END select DATEDIFF(ms, @startTime, GETDATE()) CLOSE tempCursor DEALLOCATE tempCursor ROLLBACK DROP TABLE #temp 

a única atualização leva 156 ms enquanto o cursor leva 2016 ms.