sql >> Base de Datos >  >> RDS >> Database

¿Qué impacto pueden tener las diferentes opciones del cursor?

He escrito varias veces sobre el uso de cursores y cómo, en la mayoría de los casos, es más eficiente volver a escribir los cursores usando una lógica basada en conjuntos.

Sin embargo, soy realista.

Sé que hay casos en los que los cursores son "requeridos":necesita llamar a otro procedimiento almacenado o enviar un correo electrónico para cada fila, está realizando tareas de mantenimiento en cada base de datos o está ejecutando una tarea única que simplemente no vale la pena invertir el tiempo para convertir a basado en conjuntos.

Cómo lo estás haciendo (probablemente) hoy

Independientemente de la razón por la que todavía usa cursores, al menos debe tener cuidado de no usar las opciones predeterminadas bastante caras. La mayoría de las personas comienzan sus cursores así:

DECLARE c CURSOR FOR 
  SELECT whatever FROM ...

Ahora, nuevamente, para tareas ad-hoc y únicas, esto probablemente esté bien. Pero hay…

Otras formas de hacerlo

Quería ejecutar algunas pruebas usando los valores predeterminados y compararlos con diferentes opciones de cursor como LOCAL , STATIC , READ_ONLY y FAST_FORWARD . (Hay un montón de opciones, pero estas son las más utilizadas, ya que son aplicables a los tipos más comunes de operaciones de cursor que usa la gente). No solo quería probar la velocidad bruta de algunas combinaciones diferentes, pero también el impacto en tempdb y la memoria, tanto después de un reinicio del servicio en frío como con una caché en caliente.

La consulta que decidí enviar al cursor es una consulta muy simple contra sys.objects , en la base de datos de ejemplo AdventureWorks2012. Esto devuelve 318 500 filas en mi sistema (un sistema muy humilde de 2 núcleos con 4 GB de RAM):

SELECT c1.[object_id] 
  FROM sys.objects AS c1
  CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2;

Luego envolví esta consulta en un cursor con varias opciones (incluidas las predeterminadas) y ejecuté algunas pruebas, midiendo la memoria total del servidor, las páginas asignadas a tempdb (de acuerdo con sys.dm_db_task_space_usage y/o sys.dm_db_session_space_usage ), y la duración total. También traté de observar la contención de tempdb usando scripts de Glenn Berry y Robert Davis, pero en mi insignificante sistema no pude detectar ninguna contención. Por supuesto, también estoy en SSD y no se está ejecutando absolutamente nada más en el sistema, por lo que estas pueden ser cosas que desee agregar a sus propias pruebas si es más probable que tempdb sea un cuello de botella.

Entonces, al final, las consultas se parecían a esto, con consultas de diagnóstico salpicadas en los puntos apropiados:

DECLARE @i INT = 1;
 
DECLARE c CURSOR
-- LOCAL
-- LOCAL STATIC
-- LOCAL FAST_FORWARD
-- LOCAL STATIC READ_ONLY FORWARD_ONLY
FOR
  SELECT c1.[object_id] 
    FROM sys.objects AS c1
    CROSS JOIN (SELECT TOP 500 name FROM sys.objects) AS c2
    ORDER BY c1.[object_id];
 
OPEN c;
FETCH c INTO @i;
 
WHILE (@@FETCH_STATUS = 0)
BEGIN
  SET @i += 1; -- meaningless operation
  FETCH c INTO @i;
END
 
CLOSE c;
DEALLOCATE c;

Resultados

    Duración

    Podría decirse que la medida más importante y común es "¿cuánto tiempo tomó?" Bueno, tomó casi cinco veces más ejecutar un cursor con las opciones predeterminadas (o solo con LOCAL especificado), en comparación con especificar STATIC o FAST_FORWARD :

    Memoria

    También quería medir la memoria adicional que solicitaría SQL Server al cumplir con cada tipo de cursor. Así que simplemente reinicié antes de cada prueba de caché en frío, midiendo el contador de rendimiento Total Server Memory (KB) antes y después de cada prueba. La mejor combinación aquí fue LOCAL FAST_FORWARD :

    uso de tempdb

    Este resultado me sorprendió. Dado que la definición de un cursor estático significa que copia todo el resultado en tempdb, y en realidad se expresa en sys.dm_exec_cursors como SNAPSHOT , esperaba que el éxito en las páginas de tempdb fuera mayor con todas las variantes estáticas del cursor. Este no era el caso; nuevamente vemos un éxito de aproximadamente 5X en el uso de tempdb con el cursor predeterminado y el que tiene solo LOCAL especificado:

Conclusión

Durante años he insistido en que siempre se debe especificar la siguiente opción para sus cursores:

LOCAL STATIC READ_ONLY FORWARD_ONLY

A partir de este momento, hasta que tenga la oportunidad de probar más permutaciones o encontrar algún caso en el que no sea la opción más rápida, recomendaré lo siguiente:

LOCAL FAST_FORWARD

(Aparte, también realicé pruebas omitiendo LOCAL opción, y las diferencias eran insignificantes).

Dicho esto, esto no es necesariamente cierto para *todos* los cursores. En este caso, estoy hablando únicamente de cursores en los que solo está leyendo datos del cursor, solo en una dirección hacia adelante, y no está actualizando los datos subyacentes (ya sea mediante la tecla o usando WHERE CURRENT OF ). Esas son pruebas para otro día.