Pregunta

En general, se acepta que se debe evitar el uso de cursores en procedimientos almacenados siempre que sea posible (reemplazarlos con lógica basada en conjuntos, etc.).Si toma los casos en los que necesita iterar sobre algunos datos y puede hacerlo en forma de solo lectura, ¿el cursor de avance rápido (avance de solo lectura) es más o menos ineficiente que, por ejemplo, los bucles while?Según mis investigaciones, parece que la opción del cursor es generalmente más rápida y utiliza menos lecturas y tiempo de CPU.No he realizado ninguna prueba exhaustiva, pero ¿es esto lo que otros encuentran?¿Los cursores de este tipo (avance rápido) conllevan gastos generales o recursos adicionales que podrían ser costosos y que no conozco?

¿Todo lo que se habla sobre no usar cursores es realmente sobre evitar el uso de cursores cuando hay enfoques basados ​​en conjuntos disponibles, y el uso de cursores actualizables, etc.?

Gracias

¿Fue útil?

Solución

La 'mejor práctica' de evitar cursores en SQL Server se remonta a SQL Server 2000 y versiones anteriores.La reescritura del motor en SQL 2005 abordó la mayoría de las cuestiones relacionadas con los problemas de los cursores, particularmente con la introducción de la opción de avance rápido.Los cursores no son necesariamente peores que los basados ​​en conjuntos y se utilizan ampliamente y con éxito en Oracle PL/SQL (LOOP).

El 'generalmente aceptado' al que te refieres era válido, pero ahora está desactualizado y es incorrecto: suponga que los cursores de avance rápido se comportan y funcionan como se anuncia.Realice algunas pruebas e investigue, basando sus hallazgos en SQL2005 y versiones posteriores.

Otros consejos

Si bien un cursor de avance rápido tiene algunas optimizaciones en Sql Server 2005, es no Es cierto que están cerca de una consulta basada en conjuntos en términos de rendimiento.Hay muy pocas situaciones en las que la lógica del cursor no pueda reemplazarse por una consulta basada en conjuntos.Los cursores siempre serán inherentemente más lentos, debido en parte al hecho de que tienes que seguir interrumpiendo la ejecución para completar tus variables locales.

Aquí hay algunas referencias, que sólo serían la punta del iceberg si investigas este tema:

http://www.code-magazine.com/Article.aspx?quickid=060113

http://dataeducation.com/re-inventing-the-recursive-cte/

Esta respuesta espera consolidar las respuestas dadas hasta la fecha.

1) Si es posible, utilice lógica basada en conjuntos para sus consultas, es decir,prueba y usa solo SELECT, INSERT, UPDATE o DELETE con el apropiado FROM cláusulas o consultas anidadas: casi siempre serán más rápidas.

2) Si lo anterior no es posible, entonces en SQL Server 2005+ FAST FORWARD Los cursores son eficientes y funcionan bien y deben usarse con preferencia a los bucles while.

"Si desea un cursor aún más rápido que AVANCE RÁPIDO, utilice un cursor ESTÁTICO.Son más rápidos que AVANCE RÁPIDO.No es extremadamente rápido, pero puede marcar la diferencia".

¡No tan rapido!Según Microsoft:"Por lo general, cuando se producían estas conversiones, el tipo de cursor se degradaba a un tipo de cursor 'más caro'.Generalmente, un cursor de SOLO AVANCE (RÁPIDO) es el de mayor rendimiento, seguido de DYNAMIC, KEYSET y finalmente STATIC, que generalmente es el de menor rendimiento".

de: http://blogs.msdn.com/b/mssqlisv/archive/2006/06/23/644493.aspx

Puedes evitar los cursores la mayor parte del tiempo, pero a veces es necesario.

Solo tenga en cuenta que FAST_FORWARD es DINÁMICO...FORWARD_ONLY puedes usarlo con un cursor ESTÁTICO.

¡Intenta usarlo en el problema de Halloween para ver qué pasa!

IF OBJECT_ID('Funcionarios') IS NOT NULL
DROP TABLE Funcionarios
GO

CREATE TABLE Funcionarios(ID          Int IDENTITY(1,1) PRIMARY KEY,
                          ContactName Char(7000),
                          Salario     Numeric(18,2));
GO

INSERT INTO Funcionarios(ContactName, Salario) VALUES('Fabiano', 1900)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Luciano',2050)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Gilberto', 2070)
INSERT INTO Funcionarios(ContactName, Salario) VALUES('Ivan', 2090)
GO

CREATE NONCLUSTERED INDEX ix_Salario ON Funcionarios(Salario)
GO

-- Halloween problem, will update all rows until then reach 3000 !!!
UPDATE Funcionarios SET Salario = Salario * 1.1
  FROM Funcionarios WITH(index=ix_Salario)
 WHERE Salario < 3000
GO

-- Simulate here with all different CURSOR declarations
-- DYNAMIC update the rows until all of then reach 3000
-- FAST_FORWARD update the rows until all of then reach 3000
-- STATIC update the rows only one time. 

BEGIN TRAN
DECLARE @ID INT
DECLARE TMP_Cursor CURSOR DYNAMIC 
--DECLARE TMP_Cursor CURSOR FAST_FORWARD
--DECLARE TMP_Cursor CURSOR STATIC READ_ONLY FORWARD_ONLY
    FOR SELECT ID 
          FROM Funcionarios WITH(index=ix_Salario)
         WHERE Salario < 3000

OPEN TMP_Cursor

FETCH NEXT FROM TMP_Cursor INTO @ID

WHILE @@FETCH_STATUS = 0
BEGIN
  SELECT * FROM Funcionarios WITH(index=ix_Salario)

  UPDATE Funcionarios SET Salario = Salario * 1.1 
   WHERE ID = @ID

  FETCH NEXT FROM TMP_Cursor INTO @ID
END

CLOSE TMP_Cursor
DEALLOCATE TMP_Cursor

SELECT * FROM Funcionarios

ROLLBACK TRAN
GO

La gente evita los cursores porque generalmente son más difíciles de escribir que los simples bucles while; sin embargo, un bucle while puede ser costoso porque constantemente selecciona datos de una tabla, temporal o no.

Con un cursor, que es de avance rápido de solo lectura, los datos se guardan en la memoria y han sido diseñados específicamente para bucles.

Este artículo destaca que un cursor promedio se ejecuta 50 veces más rápido que un bucle while.

Algunas alternativas al uso del cursor:

Mientras que los bucles TEMP TABLO DE DERECHO DERIVIDADES DE LA SUBCERIES ASOCIADAS DE LA CASE DE CASE Múltiples interrogaciones a menudo, las operaciones del cursor también se pueden lograr con técnicas no cursoras.

Si está seguro de que es necesario utilizar el cursor, la cantidad de registros a procesar debe reducirse tanto como sea posible.Una forma de hacerlo es hacer que los registros se procesen primero en una tabla temporal, no en la tabla original, sino en un cursor que utilizará los registros de la tabla temporal.Cuando se utiliza esta ruta, se supone que la cantidad de registros en la tabla temporal se ha reducido considerablemente en comparación con la tabla original.Con menos registros, el cursor completa más rápido.

Algunas propiedades del cursor que afectan el rendimiento incluyen:

ADELANTE_SÓLO:Admite reenviar solo el cursor desde la primera fila hasta el final con FETCH NEXT.A menos que se establezca como KEYSET o STATIC, la cláusula SELECT se vuelve a evaluar cuando se llama a cada recuperación.

ESTÁTICO:Crea una copia temporal de los datos creados y es utilizada por el cursor.Esto evita que el cursor se vuelva a calcular cada vez que se llama, lo que mejora el rendimiento.Esto no permite la modificación del tipo de cursor y los cambios en la tabla no se reflejan cuando se llama a la recuperación.

JUEGO DE LLAVES:Las filas con el cursor se colocan en una tabla bajo tempdb y los cambios en las columnas sin clave se reflejan cuando se llama a la recuperación.Sin embargo, los nuevos registros agregados a la tabla no se reflejan.Con el cursor del conjunto de claves, la instrucción SELECT no se vuelve a evaluar.

DINÁMICA:Todos los cambios en la tabla se reflejan en el cursor.El cursor se vuelve a evaluar cuando se llama a cada recuperación.Utiliza muchos recursos y afecta negativamente al rendimiento.

AVANCE RÁPIDO:El cursor es unidireccional, como FORWARD_ONLY, pero especifica el cursor como de solo lectura.FORWARD_ONLY es un aumento de rendimiento y el cursor no se reevalúa en cada recuperación.Ofrece el mejor rendimiento si es adecuado para la programación.

OPTIMISTA:Esta opción se puede utilizar para actualizar filas en el cursor.Si se recupera y actualiza una fila, y se actualiza otra fila entre las operaciones de recuperación y actualización, la operación de actualización del cursor falla.Si se utiliza un cursor OPTIMISTIC que puede realizar una actualización de línea, no debe ser actualizado por otro proceso.

NOTA:Si no se especifica el cursor, el valor predeterminado es FORWARD_ONLY.

Para responder a las preguntas originales de Mile...

Los cursores estáticos de avance rápido, solo lectura (conocidos cariñosamente como "cursor de manguera contra incendios") suelen ser tan rápidos o más rápidos que una tabla temporal equivalente y un bucle while porque dicho cursor no es más que una tabla temporal y un bucle while que Se ha optimizado un poco detrás de escena.

Para añadir a lo que Eric Z.Beard publicó en este hilo y para responder más a la pregunta de...

"¿Se habla de no usar los cursores realmente sobre evitar el uso de cursores cuando hay enfoques basados ​​en establecimientos disponibles y el uso de cursores actualizables, etc."

Sí.Con muy pocas excepciones, se necesita menos tiempo y menos código para escribir código adecuado basado en conjuntos para hacer lo mismo que la mayoría de los cursores y tiene el beneficio adicional de usar muchos menos recursos y generalmente se ejecuta MUCHO más rápido que un cursor o un bucle While.En términos generales, y con la excepción de ciertas tareas administrativas, deberían evitarse en favor de un código basado en conjuntos correctamente escrito.Por supuesto, existen excepciones a cada "regla" pero, en el caso de los cursores, los bucles while y otras formas de RBAR, la mayoría de las personas pueden contar las excepciones con una mano sin usar todos los dedos.;-)

También existe la noción de "RBAR oculto".Este es un código que parece basado en conjuntos pero que en realidad no lo es.Este tipo de código "basado en conjuntos" es la razón por la que ciertas personas han adoptado los métodos RBAR y dicen que están "bien".Por ejemplo, resolver el problema del total acumulado utilizando una subconsulta correlacionada agregada (SUM) con una desigualdad para generar el total acumulado no se basa realmente en conjuntos en mi libro.En cambio, es RBAR con esteroides porque, para cada fila calculada, tiene que "tocar" repetidamente muchas otras filas a una velocidad de N*(N+1)/2.Esto se conoce como "unión triangular" y es al menos la mitad de mala que una unión cartesiana completa (unión cruzada o "unión cuadrada").

Aunque MS ha realizado algunas mejoras en el funcionamiento de los cursores desde SQL Server 2005, el término "Cursor rápido" sigue siendo un oxímoron en comparación con el código basado en conjuntos escrito correctamente.Esto también es válido incluso en Oracle.Trabajé con Oracle durante tres años hace poco, pero mi trabajo consistía en realizar mejoras de rendimiento en el código existente.La mayoría de las mejoras realmente sustanciales se realizaron cuando convertí los cursores en código basado en conjuntos.Muchos trabajos que antes tardaban entre 4 y 8 horas en ejecutarse se redujeron a minutos y, a veces, segundos.

Si desea un cursor aún más rápido que AVANCE RÁPIDO, utilice un cursor ESTÁTICO.Son más rápidos que AVANCE RÁPIDO.No es extremadamente rápido, pero puede marcar la diferencia.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top