Documente Academic
Documente Profesional
Documente Cultură
Este articulo intentara realizar un análisis a una practica muy común en el desarrollo
de aplicaciones sobre sqlserver, sobre todo desde el lado del servidor de Base de
Datos. Antes de empezar con el análisis haremos una introducción a los cursores.
Introducción:
Un cursor es la forma de procesar los datos fila a fila en lugar de hacerlo por
conjunto de resultados. Esta técnica data desde hace varios años ya, donde muchos
desarrolladores de bases de datos como Access, FoxPro, etc. lo utilizan de forma
muy habitual. Este proceso consta de recorrer fila a fila un conjunto de resultados e
ir procesando las mismas una a una. Por Ej., podríamos tener una consulta que nos
retorna todos los Clientes y luego un proceso que recorre cliente por cliente (fila a
fila) para poder realizar en cada uno de ellos una actualización algún dato.
En las bases de datos antes mencionadas esta técnica era no solo habitual sino que
también era una de las pocas maneras que teníamos para poder resolver algunos
problemas, quien no ha usado un cursor para poder calcular balances o cosas
similares.
Todo esto esta muy bien y es muy natural para los desarrolladores de esas bases de
datos, pero ahora nos encontramos con un problema y es que las bases de datos de
antes no son las mismas que las de ahora, antes pensar en un gestor de base de
datos como SqlSErver era para algunos elegidos que podían abonar los altos costos
de equipos, licencias, etc.
Hoy día, el uso de SqlServer (ya sea su versión Standard / Entherprise o su versión
MSDE) se ha aumentado y se esta transformando en un Standard entre los
desarrolladores. Ustedes se preguntaran que tiene que ver esto con los cursores
verdad? Pues mucho mas de lo que se imaginan. Los motores de bases de datos y
en especial SqlServer están pensados y optimizados para trabajar con conjuntos de
datos y no fila a fila (Cursores) con lo cual ya tenemos aquí un problema, debemos
pensar ahora nuestras sentencias de forma tal que traten de no usar cursores ya
que dicho motor no esta optimizado para su uso. Claro, todo esto va a requerir un
conocimiento mucho mas avanzado de T-SQL y empezar a cambiar la forma de
pensar la solución a los problemas, ahora debemos casi olvidarnos de los cursores y
usarlo en cosas muy especificas.
Pues bien, se que mucho de ustedes se estarán preguntando: ¿Pero yo uso cursores
y nunca he tenido un problema? ¿Porque debería cambiar?, bueno aquí vamos a
tratar de demostrar lo malo que son los cursores y el impacto que pueden llegar a
tener sobre nuestros sistemas que corren bajo SqlServer.
Pero cuidado, tampoco vale decir “NO USAR NUNCA CURSORES”, no hay que ser
tan extremistas, solo debemos saber donde y cuando usarlos, ya que si están
disponibles es para que los usemos verdad? El tema es como y cuando, y eso
intentaremos aprender
Cursores y su creación:
Para crear cursores en SqlServer solo debemos usar algo parecido a lo siguiente:
DECLARE micursor CURSOR LOCAL FORWARD_ONLY FOR
OPEN nuestrocursor
…………………..
CLOSE nuestrocursor
DEALLOCATE nuestrocursor
Como podrán observar es algo muy común para casi todos ustedes verdad?.
• Estáticos
• Dinámicos
Test :
Bueno, ahora ya conocemos que son básicamente los cursores y que tipos tiene
SqlServer. Ahora lo que haremos es una primer prueba de su uso versus el uso de
instrucciones pensadas en el conjunto de resultados.
Los ejemplos que veremos a continuación deberán ejecutarlos desde su query
analizer.
El primer ejercicio es muy simple y la idea es mostrar con algo simple que efectos
tienen los cursores sobre el desempeño.
USE NORTHWIND
GO
SET @N = @N + 1
END
Ahora probaremos de eliminar todos los artículos C usando cursores. Para poder
medir la performance lo primero que haremos es medir cuanto tiempo demora,
como así también otros indicadores.
BEGIN TRAN
OPEN MICURSOR
WHILE @@FETCH_STATUS = 0
BEGIN
IF @TIPO = 'A'
BEGIN
DELETE FROM ARTICULOS WHERE CURRENT OF MICURSOR
END
END
ROLLBACK TRAN
Tiempo (Segundos) 10
% uso del CPU 100
Bloqueos por Seg 37134
Bien, ahora haremos la misma operación pero sin utilizar cursores y si pensando en
el conjunto de registros, veamos que sucede
BEGIN TRAN
DELETE FROM ARTICULOS WHERE TIPO='A'
ROLLBACK TRAN
Tiempo (Segundos) 3
% uso del CPU 26
Bloqueos por Seg 782
Como podrán observar entre el primer método con cursores y el segundo hay una
enorme diferencia, el primer método tardo casi el triple y además uso mucho mas
el CPU y genero muchos mas bloqueos mientras duro todo el proceso. El segundo
método ha mostrado una mejora considerable.
Este es solo un simple ejemplo con una instrucción muy simple, imagínese lo que
pasaría si fueran muchos mas registros o si el problema a resolver seria mas
complejo, pues el resultado puede ser catastrófico y que un proceso se demore
horas cuando si se hubiere pensado en conjunto de instrucciones quizás hubiere
demorado minutos.
Claro ahora nos preguntaremos, este es un simple ejemplo y lo veo fácil, pero hay
cosas que si no uso cursores no las puedo solucionar!!, pues bien, les diré algo, yo
llevo mas de 8 años con sqlserver y he implementado varios sistemas con el, les
aseguro que en producción no he utilizado ni un solo cursor, y he tenido que
resolver problemas tan complejos o mas que los comunes. Un detalle, pensar en
cursores es mucho mas fácil para los desarrolladores ya que tienen eso
incorporado, pero como hemos visto en esta primer etapa no es una muy buena
idea para el motor de base de datos, así que si se toman el tiempo para pensar el
problema sin cursores seguro que le harán un gran beneficio a sus aplicaciones
Ahora mostraremos un caso un tanto mas complejo para usar los dos métodos
(cursores y T-SQL) y comparar como se comportan cada uno.
Test2:
Bien, manos a la obra, empecemos por rearmar la tabla artículos y correr los otros
pasos para la creación de la tabla “Transacciones” y todo el llenado de esta misma
USE NORTHWIND
GO
SET @N = @N + 1
END
USE NORTHWIND
GO
USE NORTHWIND
GO
-- LLENAMOS LA TABLA TRANSACCIONES
DECLARE @CEROS INT
DECLARE @N INT
DECLARE @N2 INT
SET @N = 1
SET @CEROS = 6
SET @N2 = 1
truncate table transacciones
SET @N = @N + 1
END
Con Cursores:
BEGIN TRAN
OPEN MICURSOR
WHILE @@FETCH_STATUS = 0
BEGIN
IF @TIPO = 'A'
BEGIN
UPDATE ARTICULOS SET PRECIO = ISNULL(t2.precio,0)
FROM ARTICULOS, (SELECT T.ARTICULO_ID,T.PRECIO FROM
TRANSACCIONES T
INNER JOIN (SELECT MAX(FECHA) AS FECHA, ARTICULO_ID
FROM TRANSACCIONES WHERE ARTICULO_ID = @ID GROUP BY
ARTICULO_ID) T2 ON
T.ARTICULO_ID = T2.ARTICULO_ID AND
T.FECHA = T2.FECHA WHERE T.ARTICULO_ID=@ID) t2 where
articulos.id = t2.articulo_id and id = @id and
articulos.tipo='A'
END
IF @TIPO = 'C'
BEGIN
UPDATE ARTICULOS SET PRECIO = ISNULL(t2.precio,0)
FROM ARTICULOS, (SELECT T.ARTICULO_ID,T.PRECIO FROM
TRANSACCIONES T
INNER JOIN (SELECT MIN(FECHA) AS FECHA, ARTICULO_ID
FROM TRANSACCIONES WHERE ARTICULO_ID = @ID GROUP BY
ARTICULO_ID) T2 ON
T.ARTICULO_ID = T2.ARTICULO_ID AND
T.FECHA = T2.FECHA WHERE T.ARTICULO_ID=@ID) t2 where
articulos.id = t2.articulo_id and id = @id
and articulos.tipo='C'
END
COMMIT TRAN
Sin Cursores:
Bueno, seria interesante que corran ambos procesos y saquen sus propias
conclusiones pero en mi maquina paso lo siguiente:
Resumen:
El uso de cursores no es una técnica para nada recomendada con sqlserver, pero tampoco es
que no se deben usar nunca, por Ej.: si queremos armar un script que recorra nuestras
bases de datos y verifique por Ej. el espacio utilizado por el Log y a partir de un valor tomar
la decisión de hacerle un backup, esto seria un buen ejemplo donde el uso de cursores no
afectaría en lo mas mínimo, y la razón principal es porque tendríamos muy pocos registros,
no creo que exista un servidor con 100.000 bases de datos verdad
He visto sistemas donde tenían procesos que duraban horas (incluso uno duraba mas de
7hs) y el gran problema era que estaban pensados con cursores, se han sacado los mismos y
dichos procesos han paso a durar minutos y hasta en algunos casos segundos.
Esto es muy importante, si nuestros procesos son lentos nuestro sistema no será algo que
los usuarios quieran usar, y además pensemos que en un servidor de base de datos no
estamos solos y suelen existir otras bases de datos de otros sistemas, hacer las cosas bien
implica que nuestro sistema no sea el que quieran dar de baja por causantes de problemas o
que le ahorremos mucho dinero al cliente en compra de nuevo hardware para que nuestro
proceso de cursores dure en lugar de 7hs unas 3hs
La idea es que piensen en resolver los problemas sin el uso de cursores y que cada vez que
implementen uno piensen lo mal que le están haciendo al servidor de base de datos por ende
a la aplicación en general. Si algo no sale sin cursores vuélvanlo a pensar o consulten en las
News de MS donde siempre intentaremos ayudarlos y mas si se trata de eliminar un cursor