Por que as pessoas odeiam tanto os cursores SQL? [fechadas]

127

Eu posso entender que quero evitar ter que usar um cursor devido à sobrecarga e inconveniência, mas parece que há alguma séria fobia-cursor acontecendo, onde as pessoas estão se esforçando para evitar o uso de um.

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

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

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

Informações Relacionadas:

Cursores de avanço rápido do SQL Server

EDIT: deixe-me ser mais preciso: entendo que cursores não devem ser usados ​​em vez de operações relacionais normais ; isso é óbvio. O que eu não entendo são as pessoas que estão se esforçando para evitar cursores como eles têm cooties ou algo assim, mesmo quando um cursor é uma solução mais simples e / ou mais eficiente. É o ódio irracional que me confunde, não as eficiências técnicas óbvias.

Steven A. Lowe
fonte
1
Eu acho que o seu Edit diz tudo ... Em quase todas as situações (que eu já vi), existe uma maneira de substituir um cursor por uma situação baseada em conjunto com melhor desempenho. Você diz que não, mas você entende a diferença.
StingyJack 13/11/2008
7
Adoro as etiquetas nesta questão!
sep332 14/11/08
2
A parte sobre os limites recursivos da CTE 32é absurda. Presumivelmente, você está pensando em gatilhos recursivos e no máximo @@NESTLEVELde 32. Pode ser definido na consulta OPTION (MAXRECURSION N)com padrão 100e 0significado ilimitado.
Martin Smith
@MartinSmith: o limite padrão agora é 100, eo máximo é de 32K sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe
Não, ainda é exatamente o mesmo de quando eu fiz meu comentário e em todas as versões do SQL Server que suportam CTEs recursivas. Como o seu link diz "Quando 0 é especificado, nenhum limite é aplicado".
Martin Smith

Respostas:

74

A "sobrecarga" com cursores é apenas parte da API. Os cursores são como partes do RDBMS funcionam sob o capô. Freqüentemente CREATE TABLEe INSERTpossuem SELECTinstruções, e a implementação é a implementação óbvia do cursor interno.

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

Os cursores são anteriores aos idiomas modernos que fornecem coleções de primeira classe. C, COBOL, Fortran, etc. antigos tinham que processar linhas uma por vez, porque não havia noção de "coleção" que pudesse ser amplamente utilizada. Java, C #, Python, etc., possuem estruturas de lista de primeira classe para conter conjuntos de resultados.

The Slow Issue

Em alguns círculos, as junções relacionais são um mistério, e as pessoas escrevem cursores aninhados em vez de uma junção simples. Vi operações de loop aninhadas verdadeiramente épicas escritas como muitos e muitos cursores. Derrotar uma otimização do RDBMS. E correndo muito devagar.

SQL simples reescreve para substituir loops de cursor aninhados por junções e um único loop de cursor plano pode fazer com que os programas sejam executados 100 vezes. [Eles pensaram que eu era o deus da otimização. Tudo o que fiz foi substituir loops aninhados por junções. Cursores ainda usados.]

Essa confusão geralmente leva a uma acusação de cursores. No entanto, não é o cursor, é o mau uso 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 conjuntos não podem materializar conjuntos de resultados realmente grandes como uma única coleção na memória.

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. Segundo, o SQL é separado do aplicativo em um arquivo de configuração. Não é que os cursores sejam ruins. É que codificar todas essas aberturas, fecha e busca não é uma programação de valor agregado.

S.Lott
fonte
3
"Os cursores são como o RDBMS funciona sob o capô." Se você quer dizer especificamente o SQL Server, tudo bem, eu ignoro isso. Mas eu trabalhei nas áreas internas de vários RDBMS (e ORDBMS) (no Stonebraker) e nenhum deles fez isso. Por exemplo: Ingres usa o que equivale a "conjuntos de resultados" de tuplas internamente.
Richard T
@ Richard T: Estou trabalhando com informações de segunda mão sobre a fonte RDBMS; Vou alterar a declaração.
315/08 S.Lott
2
"Vi operações de loop aninhadas verdadeiramente épicas escritas como muitos e muitos cursores". Eu continuo vendo eles também. É difícil de acreditar.
RussellH
41

Os cursores fazem com que as pessoas apliquem excessivamente uma mentalidade processual a um ambiente baseado em conjunto.

E eles são lentos !!!

Do SQLTeam :

Observe que os cursores são a maneira MAIS LENTA de acessar dados dentro do SQL Server. Só deve ser usado quando você realmente precisar acessar uma linha por vez. A única razão pela qual posso pensar nisso é chamar um procedimento armazenado em cada linha. No artigo Cursor Performance , descobri que os cursores são trinta vezes mais lentos que as alternativas baseadas em conjuntos .

galego
fonte
6
esse artigo tem 7 anos, você acha que talvez as coisas tenham mudado nesse meio tempo?
Steven A. Lowe
1
Eu também acho que os cursores são realmente lentos e devem ser evitados em geral. No entanto, se o OP estava se referindo à pergunta que eu acho que ele era, então um cursor era a solução correta (o streaming de registros um por vez devido a restrições de memória).
Rmeador 13/11/08
o artigo atualizado não corrige as medições de velocidade relativa, mas fornece algumas boas otimizações e alternativas. Note-se que o artigo original diz que os cursores são 50 vezes mais rápido do que enquanto loops, o que é interessante
Steven A. Lowe
6
@BoltBait: Eu pessoalmente acho que se você fazer afirmações gerais como que você não pode ser realmente 45 anos :-P
Steven A. Lowe
4
@BoltBait: Vocês, crianças, saem do meu gramado!
Steven A. Lowe
19

Há uma resposta acima que diz "os cursores são a maneira MAIS LENTA de acessar dados no SQL Server ... os cursores são trinta vezes mais lentos do que as alternativas baseadas em conjunto".

Essa afirmação pode ser verdadeira em muitas circunstâncias, mas, como afirmação geral, é problemática. Por exemplo, fiz bom uso de cursores em situações nas quais desejo executar uma operação de atualização ou exclusão que afeta muitas linhas de uma tabela grande que está recebendo leituras constantes de produção. A execução de um procedimento armazenado que atualiza uma linha por vez acaba sendo mais rápida 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 completamente o sistema de produção, em casos extremos).

Na ausência de outras atividades do banco de dados, as operações baseadas em conjuntos são universalmente mais rápidas. Nos sistemas de produção, isso depende.

davidcl
fonte
1
Parece a exceção que prova a regra.
Joel Coehoorn
6
@ [Joel Coehoorn]: Eu nunca entendi esse ditado.
9788 Steven A. Lowe
2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html entendem a exceção como "o que é deixado de fora" e observe que a regra aqui é algo como "na maioria dos casos os cursores são ruim".
David Lay
1
@ delm: obrigado pelo link, agora eu entendo a frase ainda menos!
9788 Steven A. Lowe
5
@ [Steven A. Lowe] Basicamente, está dizendo que se você "quebra uma regra" com um subcaso, deve haver uma regra geral a ser quebrada, logo, existe uma regra. Por exemplo, no link: ("Se tivermos uma declaração como 'entrada gratuita aos domingos', podemos razoavelmente assumir que, como regra geral, a entrada é cobrada.")
Fry
9

Os cursores tendem a ser usados ​​iniciando desenvolvedores SQL em locais onde as operações baseadas em conjuntos seriam melhores. Particularmente quando as pessoas aprendem SQL após aprenderem uma linguagem de programação tradicional, a mentalidade "iterar sobre esses registros" tende a levar as pessoas a usar cursores de maneira inadequada.

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

Obviamente, existem situações em que os cursores são a escolha correta ou pelo menos A escolha correta.

davidcl
fonte
9

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

Cade Roux
fonte
8

No Oracle PL / SQL, os cursores não resultam em bloqueios de tabela e é possível usar a coleta / busca em massa.

No Oracle 10, o cursor implícito frequentemente usado

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

busca implicitamente 100 linhas por vez. Também é possível coletar / buscar em massa explícita.

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

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

tuinstoel
fonte
6

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

Charles Bretana
fonte
você tem uma referência ou referência para isso? Eu não notei nenhuma degradação drástica de desempenho ... mas talvez minhas tabelas não tenham linhas suficientes para importar (um milhão ou menos, geralmente)?
9788 Steven A. Lowe
oh wait i ver o que você quer dizer - mas eu nunca iria defender utilizar cursores intead de operações de conjunto, só que não vai a extremos para cursores Evitar
Steven A. Lowe
3
Lembro-me da primeira vez que fiz o SQL. Tivemos que importar um arquivo de dados diários de 50k de um mainframe para um banco de dados do SQL Server ... Usei um cursor e descobri que a importação estava demorando cerca de 26 horas usando o cursor. Quando mudei para operações baseadas em conjunto, o processo levou 20 minutos.
Charles Bretana 13/11/08
6

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 no nível da tabela.

Richard T
fonte
1
sim obrigado Sem opções para evitá-lo (somente leitura, somente encaminhamento, etc), eles certamente irão, assim como qualquer operação (servidor sql) que passe a ocupar várias linhas e depois várias páginas de linhas.
Steven A. Lowe
?? Esse é um problema com sua estratégia de bloqueio, NÃO com cursores. Mesmo uma instrução SELECT adicionará bloqueios de leitura.
Adam
3

Pelo que vale a pena, eu li que o "único" lugar em que um cursor irá executar sua contraparte baseada em conjunto está em um total contínuo. Em uma tabela pequena, a velocidade de somar as linhas acima da ordem por colunas favorece a operação baseada em conjunto, mas à medida que a tabela aumenta o tamanho da linha, o cursor se torna mais rápido, pois pode simplesmente levar o valor total em execução para a próxima passagem do ciclo. Agora, onde você deve fazer um total de execução, há um argumento diferente ...

Eric Sabine
fonte
1
Se você quer dizer "execução total" de algum tipo de agregação (min, max, soma), qualquer DBMS competente eliminará uma solução baseada em cursor do lado do cliente, apenas porque a função é executada no mecanismo e não há sobrecarga do servidor do cliente. Talvez o SQL Server não seja competente?
Richard T
1
@ [Richard T]: estamos discutindo cursores do lado do servidor, como dentro de um procedimento armazenado, não cursores do lado do cliente; Desculpe pela confusão!
Steven A. Lowe
2

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 depuração tende a ser relativamente fácil e os recursos de idioma tendem a ser muito mais fáceis. Na verdade, eu afirmo que quase tudo que alguém está fazendo no SQL com um cursor provavelmente deve estar acontecendo no aplicativo cliente em primeiro lugar.

Wyatt Barnett
fonte
2
É difícil depurar o SQL, mesmo sem cursores. As ferramentas passo a passo do MS SQL no Visual Studio parecem não gostar de mim (elas paralisam muito ou não desarmam nada), por isso geralmente sou reduzido a instruções PRINT ;-)
Steven A. Lowe
1

Você pode postar esse exemplo de cursor ou link para a pergunta? Provavelmente existe uma maneira ainda melhor do que uma CTE recursiva.

Além de outros comentários, os cursores quando usados ​​incorretamente (geralmente) causam bloqueios desnecessários de páginas / linhas.

Gordon Bell
fonte
1
há uma maneira melhor - um freakin' cursor ;-)
Steven A. Lowe
1

Provavelmente, você 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 que você e tentando zombar de profissionais que possam ter um bom motivo para se sentirem assim.

Quanto à sua pergunta, embora certamente haja situações em que um cursor possa ser solicitado, na minha experiência, os desenvolvedores decidem que um cursor "deve" ser usado MUITO MAIS frequentemente do que é realmente o caso. A chance de alguém errar por excesso de uso de cursores vs. não usá-lo quando deveria é MUITO maior na minha opinião.

Tom H
fonte
8
por favor, leia com mais atenção, Tom - a frase exata era "ódio insano"; "odiado" era o objeto do adjetivo "insano", não "pessoas". Inglês pode ser um pouco às vezes difíceis ;-)
Steven A. Lowe
0

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 atualização única leva 156 ms enquanto o cursor leva 2016 ms.

Mladen Prajdic
fonte
3
Bem, sim, isso prova que essa é uma maneira realmente idiota de usar um cursor! mas e se a atualização de cada linha dependesse do valor da linha anterior na ordem da data?
Steven A. Lowe
BEGIN TRAN SELECT TOP 1 baseval DE ORDEM mesa por Timestamp mesa DESC INSERIR (campos) VALUES (vals, incluindo valor derivado do recorde anterior) COMMIT TRAN
dkretz
@doofledorfer: que iria inserir uma linha baseada na última linha por data, não atualizar cada linha por um valor a partir de sua linha anterior em ordem de data
Steven A. Lowe
Para realmente usar o cursor, você deve usar WHERE CURRENT OF na atualização #
erikkallen