¿Por qué la gente odia tanto los cursores SQL? [cerrado]

127

Puedo entender querer evitar tener que usar un cursor debido a la sobrecarga y las molestias, pero parece que está ocurriendo una seria fobia al cursor donde la gente hace todo lo posible para evitar tener que usar uno.

Por ejemplo, una pregunta preguntó cómo hacer algo obviamente trivial con un cursor y la respuesta aceptada propuso utilizar una consulta recursiva de expresión de tabla común (CTE) con una función personalizada recursiva, aunque esto limita el número de filas que podrían procesarse a 32 (debido al límite de llamadas a funciones recursivas en el servidor sql). Esto me parece una solución terrible para la longevidad del sistema, sin mencionar un tremendo esfuerzo solo para evitar usar un cursor simple.

¿Cuál es la razón de este nivel de odio loco? ¿Alguna 'autoridad notoria' ha emitido una fatwa contra los cursores? ¿Algún mal indescriptible acecha en el corazón de los cursores que corrompe la moral de los niños o algo así?

Pregunta de Wiki, más interesado en la respuesta que en el representante.

Información relacionada:

Cursores de avance rápido de SQL Server

EDITAR: permítanme ser más preciso: entiendo que los cursores no deben usarse en lugar de las operaciones relacionales normales ; eso es obvio. Lo que no entiendo es que las personas se desviven mucho para evitar cursores como si tuvieran piojos o algo así, incluso cuando un cursor es una solución más simple y / o más eficiente. Es el odio irracional lo que me desconcierta, no las eficiencias técnicas obvias.

Steven A. Lowe
fuente
1
Creo que su edición lo dice todo ... En casi todas las situaciones (que he encontrado) hay una manera de reemplazar un cursor con una situación basada en un conjunto de mejor rendimiento. Dices obvio, pero entiendes la diferencia.
StingyJack
77
¡Me encantan las etiquetas de esta pregunta!
sep332
2
La parte sobre los límites recursivos de CTE 32es una tontería. Presumiblemente estás pensando en disparadores recursivos y el máximo @@NESTLEVELde 32. Se puede configurar en la consulta OPTION (MAXRECURSION N)con el valor predeterminado 100e 0ilimitado.
Martin Smith
@MartinSmith: el límite predeterminado ahora es 100, y el máximo es 32K sql-server-helper.com/error-messages/msg-310.aspx
Steven A. Lowe
No, sigue siendo exactamente igual que cuando hice mi comentario y en todas las versiones de SQL Server que admiten CTE recursivos. Como su enlace dice "Cuando se especifica 0, no se aplica ningún límite".
Martin Smith

Respuestas:

74

La "sobrecarga" con los cursores es simplemente parte de la API. Los cursores son cómo funcionan las partes del RDBMS debajo del capó. A menudo CREATE TABLEy INSERTtienen SELECTdeclaraciones, y la implementación es la implementación del cursor interno obvio.

El uso de "operadores basados ​​en conjuntos" de nivel superior agrupa los resultados del cursor en un único conjunto de resultados, lo que significa menos API de ida y vuelta.

Los cursores son anteriores a los idiomas modernos que proporcionan colecciones de primera clase. Old C, COBOL, Fortran, etc., tuvieron que procesar las filas una a la vez porque no había una noción de "colección" que pudiera usarse ampliamente. Java, C #, Python, etc., tienen estructuras de listas de primera clase para contener conjuntos de resultados.

El problema lento

En algunos círculos, las uniones relacionales son un misterio, y la gente escribirá cursores anidados en lugar de una simple unión. He visto operaciones de bucle anidado verdaderamente épicas escritas como montones y montones de cursores. Derrotando una optimización RDBMS. Y corriendo muy despacio.

Las reescrituras simples de SQL para reemplazar los bucles de cursor anidados con combinaciones y un solo bucle de cursor plano pueden hacer que los programas se ejecuten en la centésima vez. [Pensaban que yo era el dios de la optimización. Todo lo que hice fue reemplazar los bucles anidados con uniones. Todavía usé cursores.]

Esta confusión a menudo conduce a una acusación de cursores. Sin embargo, no es el cursor, es el mal uso del cursor el problema.

El problema del tamaño

Para conjuntos de resultados realmente épicos (es decir, volcar una tabla en un archivo), los cursores son esenciales. Las operaciones basadas en conjuntos no pueden materializar conjuntos de resultados realmente grandes como una sola colección en la memoria.

Alternativas

Intento usar una capa ORM tanto como sea posible. Pero eso tiene dos propósitos. Primero, los cursores son administrados por el componente ORM. En segundo lugar, el SQL se separa de la aplicación en un archivo de configuración. No es que los cursores sean malos. Es que codificar todas esas aperturas, cierres y recuperaciones no es una programación de valor agregado.

revs S.Lott
fuente
3
"Los cursores son cómo funciona el RDBMS". Si te refieres específicamente a SQL Server, está bien, lo ignoro. Pero he trabajado en las partes internas de múltiples RDBMS (y ORDBMS) (bajo Stonebraker) y ninguno de ellos lo hizo. Por ejemplo: Ingres utiliza lo que equivale a "conjuntos de resultados" de tuplas internamente.
Richard T
@ Richard T: Estoy trabajando con información de segunda mano sobre la fuente RDBMS; Enmendaré la declaración.
S.Lott
2
"He visto operaciones de bucle anidado verdaderamente épicas escritas como montones y montones de cursores". Yo también los sigo viendo. Es dificil de creer.
RussellH
41

Los cursores hacen que las personas apliquen excesivamente una mentalidad procesal a un entorno basado en conjuntos.

¡Y son LENTOS !

De SQLTeam :

Tenga en cuenta que los cursores son la forma MÁS LENTA de acceder a los datos dentro de SQL Server. Solo debe usarse cuando realmente necesite acceder a una fila a la vez. La única razón por la que puedo pensar es para llamar a un procedimiento almacenado en cada fila. En el artículo Cursor Performance descubrí que los cursores son treinta veces más lentos que las alternativas basadas en conjuntos .

galés
fuente
66
ese artículo tiene 7 años, ¿crees que quizás las cosas podrían haber cambiado mientras tanto?
Steven A. Lowe
1
También creo que los cursores son realmente lentos y que deben evitarse, en general. Sin embargo, si el OP se refería a la pregunta que creo que era, entonces un cursor era la solución correcta allí (la transmisión de registros de uno en uno debido a restricciones de memoria).
rmeador
El artículo actualizado no corrige las mediciones de velocidad relativa, pero proporciona algunas buenas optimizaciones y alternativas. Tenga en cuenta que el artículo original dice que los cursores son 50 veces más rápidos que los bucles while, lo cual es interesante
Steven A. Lowe
66
@BoltBait: Personalmente creo que si haces afirmaciones generales como esa, no puedes tener 45 años :-P
Steven A. Lowe
44
@BoltBait: ¡Chicos, salgan de mi césped!
Steven A. Lowe
19

Hay una respuesta anterior que dice "los cursores son la forma MÁS LENTA de acceder a los datos dentro de SQL Server ... los cursores son treinta veces más lentos que las alternativas basadas en conjuntos".

Esta afirmación puede ser cierta en muchas circunstancias, pero como afirmación general es problemática. Por ejemplo, he hecho buen uso de los cursores en situaciones en las que deseo realizar una operación de actualización o eliminación que afecta a muchas filas de una tabla grande que recibe lecturas de producción constantes. La ejecución de un procedimiento almacenado que realiza estas actualizaciones una fila a la vez termina siendo más rápido que las operaciones basadas en conjuntos, porque la operación basada en conjuntos entra en conflicto con la operación de lectura y termina causando problemas de bloqueo horribles (y puede matar el sistema de producción por completo, en casos extremos).

En ausencia de otra actividad de base de datos, las operaciones basadas en conjuntos son universalmente más rápidas. En los sistemas de producción, depende.

revs davidcl
fuente
1
Suena como la excepción que prueba la regla.
Joel Coehoorn el
66
@ [Joel Coehoorn]: Nunca he entendido ese dicho.
Steven A. Lowe
2
@ [Steven A. Lowe] phrases.org.uk/meanings/exception-that-proves-the-rule.html~~MD~~aux~~singular~~3rd entender excepción, ya que "lo que se quede fuera" y nota que la regla aquí es algo así como "en la mayoría de los cursores situación son malo".
David Lay
1
@delm: gracias por el enlace, ¡ahora entiendo la frase aún menos!
Steven A. Lowe
55
@ [Steven A. Lowe] Básicamente está diciendo que si "infringe una regla" con un subcase, debe haber una regla general para romper, ergo, existe una regla. Por ejemplo, desde el enlace: ("Si tenemos una declaración como 'la entrada es gratuita los domingos', podemos suponer razonablemente que, como regla general, se cobra la entrada".)
Freír el
9

Los desarrolladores de SQL suelen utilizar los cursores en lugares donde las operaciones basadas en conjuntos serían mejores. Particularmente cuando las personas aprenden SQL después de aprender un lenguaje de programación tradicional, la mentalidad de "iterar sobre estos registros" tiende a llevar a las personas a usar cursores inapropiadamente.

Los libros SQL más serios incluyen un capítulo que ordena el uso de cursores; los bien escritos dejan en claro que los cursores tienen su lugar pero no deben usarse para operaciones basadas en conjuntos.

Obviamente, hay situaciones en las que los cursores son la elección correcta, o al menos una elección correcta.

davidcl
fuente
9

El optimizador a menudo no puede usar el álgebra relacional para transformar el problema cuando se usa un método de cursor. A menudo, un cursor es una excelente manera de resolver un problema, pero SQL es un lenguaje declarativo, y hay mucha información en la base de datos, desde restricciones hasta estadísticas e índices, lo que significa que el optimizador tiene muchas opciones para resolver el problema. problema, mientras que un cursor dirige explícitamente la solución.

Cade Roux
fuente
8

En Oracle, los cursores PL / SQL no generarán bloqueos de tabla y es posible utilizar la recolección masiva / la obtención masiva.

En Oracle 10, el cursor implícito de uso frecuente

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

recupera implícitamente 100 filas a la vez. También es posible la recolección masiva explícita / la obtención masiva.

Sin embargo, los cursores PL / SQL son un último recurso, úselos cuando no pueda resolver un problema con SQL basado en conjuntos.

Otra razón es la paralelización, es más fácil para la base de datos para paralelizar grandes declaraciones basadas en conjuntos que el código imperativo fila por fila. Es la misma razón por la que la programación funcional se vuelve cada vez más popular (Haskell, F #, Lisp, C # LINQ, MapReduce ...), la programación funcional facilita la paralelización. El número de CPU por computadora está aumentando, por lo que la paralelización se convierte cada vez más en un problema.

tuinstoel
fuente
6

En general, porque en una base de datos relacional, el rendimiento del código usando cursores es un orden de magnitud peor que las operaciones basadas en conjuntos.

Charles Bretana
fuente
¿tiene un punto de referencia o referencia para esto? No he notado una degradación tan drástica del rendimiento ... ¿pero tal vez mis tablas no tienen suficientes filas para que importe (un millón o menos, por lo general)?
Steven A. Lowe
oh, espera, entiendo lo que quieres decir, pero nunca recomendaría el uso de cursores en lugar de operaciones de conjunto, solo que no iría a los extremos para evitar cursores
Steven A. Lowe
3
Recuerdo la primera vez que hice SQL, tuvimos que importar un archivo de datos diario de 50k desde un mainframe a una base de datos de SQL Server ... Utilicé un cursor y descubrí que la importación tardaba unas 26 horas usando el cursor ... Cuando cambié a operaciones basadas en conjuntos, el proceso tomó 20 minutos.
Charles Bretana el
6

Las respuestas anteriores no han enfatizado lo suficiente la importancia del bloqueo. No soy un gran fanático de los cursores porque a menudo resultan en bloqueos a nivel de tabla.

Richard T
fuente
1
¡si, gracias! Sin opciones para evitarlo (solo lectura, solo reenvío, etc.) ciertamente lo harán, al igual que cualquier operación (servidor SQL) que proceda a ocupar varias filas y luego varias páginas de filas.
Steven A. Lowe
?? Ese es un problema con su estrategia de bloqueo NO con los cursores. Incluso una instrucción SELECT agregará bloqueos de lectura.
Adam
3

Por lo que vale, he leído que el "único" lugar donde un cursor superará a su contraparte basada en conjuntos es un total acumulado. En una tabla pequeña, la velocidad de resumir las filas en el orden por columnas favorece la operación basada en conjuntos, pero a medida que la tabla aumenta en tamaño de fila, el cursor se volverá más rápido porque simplemente puede llevar el valor total acumulado al siguiente paso del lazo. Ahora, donde debe hacer un total acumulado es un argumento diferente ...

Eric Sabine
fuente
1
Si quiere decir con "total acumulado" una agregación de algún tipo (min, max, sum), cualquier DBMS competente superará a una solución basada en el cursor del lado del cliente, aunque solo sea porque la función se realiza en el motor y no hay sobrecarga del servidor del cliente <-->. ¿Quizás SQL Server no es competente?
Richard T
1
@ [Richard T]: estamos discutiendo los cursores del lado del servidor, como dentro de un procedimiento almacenado, no los cursores del lado del cliente; ¡perdón por la confusion!
Steven A. Lowe
2

Fuera de los problemas de rendimiento (no), creo que la mayor falla de los cursores es que son dolorosos de depurar. Especialmente comparado con el código en la mayoría de las aplicaciones cliente, donde la depuración tiende a ser relativamente fácil y las características del lenguaje tienden a ser mucho más fáciles. De hecho, afirmo que casi todo lo que uno está haciendo en SQL con un cursor probablemente debería estar sucediendo en la aplicación cliente en primer lugar.

Wyatt Barnett
fuente
2
SQL es doloroso de depurar, incluso sin cursores. Las herramientas paso a paso de MS SQL en Visual Studio no parecen gustarme (cuelgan mucho o no disparan puntos de interrupción en absoluto), por lo que generalmente me reduzco a declaraciones PRINT ;-)
Steven A. Lowe
1

¿Puedes publicar ese ejemplo de cursor o un enlace a la pregunta? Probablemente haya una forma aún mejor que un CTE recursivo.

Además de otros comentarios, los cursores cuando se usan de manera inadecuada (que a menudo) causan bloqueos innecesarios de página / fila.

Gordon Bell
fuente
1
hay una mejor manera - un maldito cursor ;-)
Steven A. Lowe
1

Probablemente podría haber concluido su pregunta después del segundo párrafo, en lugar de llamar a las personas "locas" simplemente porque tienen un punto de vista diferente al suyo y de otra manera tratar de burlarse de los profesionales que pueden tener una muy buena razón para sentirse como ellos.

En cuanto a su pregunta, aunque ciertamente hay situaciones en las que se puede requerir un cursor, en mi experiencia los desarrolladores deciden que un cursor "debe" usarse MUCHO más a menudo de lo que realmente es el caso. En mi opinión, la posibilidad de que alguien cometa un error al usar demasiado los cursores en lugar de no usarlos cuando deberían debería ser MUCHO mayor.

Tom H
fuente
8
lee más detenidamente, Tom: la frase exacta era "odio loco"; "odiado" era el objeto del adjetivo "loco", no "personas". El inglés puede ser un poco difícil a veces ;-)
Steven A. Lowe
0

Básicamente 2 bloques de código que hacen lo mismo. Tal vez es un ejemplo un poco extraño, pero demuestra el punto. 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

la actualización individual tarda 156 ms mientras que el cursor tarda 2016 ms.

Mladen Prajdic
fuente
3
bueno, sí, ¡demuestra que esta es una forma realmente tonta de usar un cursor! pero ¿qué pasa si la actualización de cada fila dependía del valor de la fila anterior en el orden de la fecha?
Steven A. Lowe
COMIENZO TRAN SELECCIONAR TOP1 baseval DESDE tabla ORDEN POR marca de tiempo DESC INSERTAR tabla (campos) VALORES (vals, incluido el valor derivado del registro anterior) COMMIT TRAN
dkretz
@doofledorfer: eso insertaría una fila basada en la última fila por fecha, no actualizaría cada fila por un valor de su fila anterior en orden de fecha
Steven A. Lowe
Para usar realmente el cursor, debe usar WHERE CURRENT OF en la actualización
erikkallen