Pergunta

Eu sabia de alguns motivos de desempenho nos 7 dias do SQL, mas os mesmos problemas ainda existem no SQL Server 2005?Se eu tiver um conjunto de resultados em um procedimento armazenado sobre o qual desejo atuar individualmente, os cursores ainda são uma má escolha?Se sim, por quê?

Foi útil?

Solução

Porque os cursores ocupam memória e criam bloqueios.

O que você realmente está fazendo é tentar forçar a tecnologia baseada em conjuntos a uma funcionalidade não baseada em conjuntos.E, com toda a justiça, devo salientar que os cursores fazer têm utilidade, mas são desaprovados porque muitas pessoas que não estão acostumadas a usar soluções baseadas em conjuntos usam cursores em vez de descobrir a solução baseada em conjuntos.

Mas, quando você abre um cursor, você basicamente carrega essas linhas na memória e as bloqueia, criando blocos potenciais.Então, conforme você percorre o cursor, você faz alterações em outras tabelas e ainda mantém toda a memória e bloqueios do cursor abertos.

Tudo isso tem o potencial de causar problemas de desempenho para outros usuários.

Portanto, como regra geral, os cursores são desaprovados.Especialmente se essa for a primeira solução encontrada para resolver um problema.

Outras dicas

Os comentários acima sobre o SQL ser um ambiente baseado em conjunto são todos verdadeiros.No entanto, há momentos em que as operações linha por linha são úteis.Considere uma combinação de metadados e SQL dinâmico.

Como um exemplo muito simples, digamos que eu tenha mais de 100 registros em uma tabela que definem os nomes das tabelas que desejo copiar/truncar/qualquer coisa.Qual é melhor?Codificando o SQL para fazer o que preciso?Ou iterar por esse conjunto de resultados e usar SQL dinâmico (sp_executesql) para executar as operações?

Não há como atingir o objetivo acima usando SQL baseado em conjunto.

Então, usar cursores ou um loop while (pseudocursores)?

Os cursores SQL funcionam bem, desde que você use as opções corretas:

INSENSITIVE fará uma cópia temporária do seu conjunto de resultados (evitando que você tenha que fazer isso sozinho para o seu pseudo-cursor).

READ_ONLY garantirá que nenhum bloqueio seja mantido no conjunto de resultados subjacente.As alterações no conjunto de resultados subjacente serão refletidas nas buscas subsequentes (o mesmo que obter o TOP 1 do seu pseudo-cursor).

FAST_FORWARD criará um cursor otimizado somente para leitura e avanço.

Leia sobre as opções disponíveis antes de considerar todos os cursores maus.

Existe uma solução alternativa sobre cursores que uso sempre que preciso de um.

Eu crio uma variável de tabela com uma coluna de identidade.

insira todos os dados que preciso para trabalhar nele.

Em seguida, faça um bloco while com uma variável de contador e selecione os dados desejados da variável de tabela com uma instrução select onde a coluna de identidade corresponde ao contador.

Assim não bloqueio nada e uso muito menos memória e é seguro, não vou perder nada com corrupção de memória ou algo parecido.

E o código de bloco é fácil de ver e manusear.

Este é um exemplo simples:

DECLARE @TAB TABLE(ID INT IDENTITY, COLUMN1 VARCHAR(10), COLUMN2 VARCHAR(10))

DECLARE @COUNT INT,
        @MAX INT, 
        @CONCAT VARCHAR(MAX), 
        @COLUMN1 VARCHAR(10), 
        @COLUMN2 VARCHAR(10)

SET @COUNT = 1

INSERT INTO @TAB VALUES('TE1S', 'TE21')
INSERT INTO @TAB VALUES('TE1S', 'TE22')
INSERT INTO @TAB VALUES('TE1S', 'TE23')
INSERT INTO @TAB VALUES('TE1S', 'TE24')
INSERT INTO @TAB VALUES('TE1S', 'TE25')

SELECT @MAX = @@IDENTITY

WHILE @COUNT <= @MAX BEGIN
    SELECT @COLUMN1 = COLUMN1, @COLUMN2 = COLUMN2 FROM @TAB WHERE ID = @COUNT

    IF @CONCAT IS NULL BEGIN
        SET @CONCAT = '' 
    END ELSE BEGIN 
        SET @CONCAT = @CONCAT + ',' 
    END

    SET @CONCAT = @CONCAT + @COLUMN1 + @COLUMN2

    SET @COUNT = @COUNT + 1
END

SELECT @CONCAT

Acho que os cursores recebem uma má reputação porque os novatos em SQL os descobrem e pensam "Ei, um loop for!Eu sei como usá-los!" e então eles continuam a usá-los para tudo.

Se você os usar para a finalidade para a qual foram projetados, não consigo encontrar falhas nisso.

SQL é uma linguagem baseada em conjuntos – é isso que ela faz de melhor.

Acho que os cursores ainda são uma má escolha, a menos que você entenda o suficiente sobre eles para justificar seu uso em circunstâncias limitadas.

Outra razão pela qual não gosto de cursores é a clareza.O bloco do cursor é tão feio que é difícil de usar de forma clara e eficaz.

Tudo o que foi dito, há são alguns casos em que um cursor é realmente melhor - geralmente não são os casos em que os iniciantes desejam usá-los.

Às vezes, a natureza do processamento que você precisa executar requer cursores, embora por motivos de desempenho seja sempre melhor escrever a(s) operação(ões) usando lógica baseada em conjunto, se possível.

Eu não chamaria de "má prática" usar cursores, mas eles consomem mais recursos no servidor (do que uma abordagem equivalente baseada em conjunto) e na maioria das vezes não são necessários.Diante disso, meu conselho seria considerar outras opções antes de recorrer ao cursor.

Existem vários tipos de cursores (somente encaminhamento, estático, conjunto de chaves, dinâmico).Cada um possui diferentes características de desempenho e sobrecarga associada.Certifique-se de usar o tipo de cursor correto para sua operação.Somente encaminhamento é o padrão.

Um argumento para usar um cursor é quando você precisa processar e atualizar linhas individuais, especialmente para um conjunto de dados que não possui uma boa chave exclusiva.Nesse caso você pode usar a cláusula FOR UPDATE ao declarar o cursor e processar atualizações com UPDATE ...DE ONDE ESTÁ ATUAL.

Observe que os cursores do "lado do servidor" costumavam ser populares (de ODBC e OLE DB), mas o ADO.NET não os suporta, e o AFAIK nunca o fará.

@ Daniel P -> você não precisa usar cursor para fazer isso.Você pode facilmente usar a teoria baseada em conjuntos para fazer isso.Por exemplo:com SQL 2008

DECLARE @commandname NVARCHAR(1000) = '';

SELECT @commandname += 'truncate table ' + tablename + '; ';
FROM tableNames;

EXEC sp_executesql @commandname;

simplesmente fará o que você disse acima.E você pode fazer o mesmo com o SQL 2000, mas a sintaxe da consulta seria diferente.

No entanto, meu conselho é evitar cursores tanto quanto possível.

Gayam

Existem muito poucos casos em que o uso de um cursor é justificado.Quase não há casos em que o desempenho seja superior ao de uma consulta relacional baseada em conjunto.Às vezes é mais fácil para um programador pensar em termos de loops, mas o uso da lógica definida, por exemplo, para atualizar um grande número de linhas em uma tabela, resultará em uma solução que não é apenas muito menos linhas de código SQL, mas isso funciona muito mais rápido, muitas vezes várias ordens de grandeza mais rápido.

Mesmo o cursor de avanço rápido no Sql Server 2005 não pode competir com consultas baseadas em conjuntos.O gráfico de degradação de desempenho muitas vezes começa a parecer uma operação n^2 em comparação com a operação baseada em conjunto, que tende a ser mais linear à medida que o conjunto de dados cresce muito.

Os cursores têm seu lugar, mas acho que é principalmente porque eles são frequentemente usados ​​quando uma única instrução select seria suficiente para fornecer agregação e filtragem de resultados.

Evitar cursores permite que o SQL Server otimize de forma mais completa o desempenho da consulta, o que é muito importante em sistemas maiores.

Os cursores geralmente não são a doença, mas um sintoma dela:não usar a abordagem baseada em conjuntos (conforme mencionado nas outras respostas).

Não entender esse problema e simplesmente acreditar que evitar o cursor “malvado” irá resolvê-lo pode piorar as coisas.

Por exemplo, substituir a iteração do cursor por outro código iterativo, como mover dados para tabelas temporárias ou variáveis ​​de tabela, para fazer um loop nas linhas de uma forma como:

SELECT * FROM @temptable WHERE Id=@counter 

ou

SELECT TOP 1 * FROM @temptable WHERE Id>@lastId

Tal abordagem, conforme mostrado no código de outra resposta, piora muito as coisas e não resolve o problema original.É um antipadrão chamado programação de culto à carga:não saber POR QUE algo está ruim e assim implementar algo pior para evitá-lo!Recentemente, alterei esse código (usando um #temptable e nenhum índice em identidade/PK) de volta para um cursor, e atualizar um pouco mais de 10.000 linhas levou apenas 1 segundo em vez de quase 3 minutos.Ainda falta abordagem baseada em conjuntos (sendo o mal menor), mas o melhor que pude fazer naquele momento.

Outro sintoma desta falta de compreensão pode ser o que às vezes chamo de “doença de um objeto”:aplicativos de banco de dados que lidam com objetos únicos por meio de camadas de acesso a dados ou mapeadores relacionais de objetos.Normalmente código como:

var items = new List<Item>();
foreach(int oneId in itemIds)
{
    items.Add(dataAccess.GetItemById(oneId);
}

em vez de

var items = dataAccess.GetItemsByIds(itemIds);

O primeiro geralmente inundará o banco de dados com toneladas de SELECTs, uma viagem de ida e volta para cada, especialmente quando árvores/gráficos de objetos entram em jogo e o infame problema SELECT N+1 ocorre.

Este é o lado do aplicativo de não entender os bancos de dados relacionais e a abordagem baseada em conjuntos, da mesma forma que os cursores são ao usar código de banco de dados processual, como T-SQL ou PL/SQL!

A questão básica, penso eu, é que os bancos de dados são projetados e ajustados para operações baseadas em conjuntos – seleções, atualizações e exclusões de grandes quantidades de dados em uma única etapa rápida com base nas relações nos dados.

O software na memória, por outro lado, é projetado para operações individuais, portanto, fazer um loop em um conjunto de dados e potencialmente executar operações diferentes em cada item em série é o que ele faz de melhor.

O loop não é para o qual o banco de dados ou a arquitetura de armazenamento foram projetados e, mesmo no SQL Server 2005, você não obterá desempenho nem perto do obtido se extrair os dados básicos definidos em um programa personalizado e fizer o loop na memória , usando objetos/estruturas de dados tão leves quanto possível.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top