Published on

December 20, 2014

Comprendiendo los cursores de SQL Server y la API Fetch

En un reciente ejercicio de depuración de rendimiento, uno de mis amigos me hizo una pregunta: “Cuando veo el monitor de actividad, bajo las consultas costosas recientes y bajo la columna de consulta, veo ‘fetch api_cursor0000000000000003’. ¿Qué es esto? ¿Cómo puedo saber qué se estaba ejecutando detrás de esta consulta?”

Obviamente, la pista de esa pregunta eran las palabras clave “Fetch y Cursor”. Así que sabía cuál sería el punto de partida. ¿Cuántas veces te has encontrado en una situación en la que quieres rastrear una consulta pero cuando capturas el perfilador o usas métodos convencionales como sys.sysprocesses o DBCC INPUTBUFFER, ves algo como esto:

Lo mismo en el perfilador se vería así:

EXEC sp_cursorfetch 180150003 , 16 , 8 , 1
O
FETCH API_CURSOR0000000000000001

Nota: El número en la consulta puede variar en tu entorno.

Aquí tienes un código VBScript para simular el problema:

Dim strConnection
Dim MyConnection
Dim MyRecordSet
Dim strSQL

strConnection = "Provider=SQLOLEDB;Data Source=.\SQL2014;Initial Catalog=Master;Integrated Security=SSPI;"
Set MyConnection = CreateObject("ADODB.Connection")
MyConnection.Open strConnection
Set MyRecordSet = CreateObject("ADODB.recordset")
strSQL = "SELECT TOP 3 * FROM sysobjects"
MyRecordSet.Cursorlocation = 2
MyRecordSet.Open strSQL, MyConnection, 3,3

MyRecordSet.MoveFirst
WHILE NOT MyRecordSet.EOF
    MsgBox(MyRecordSet("name").value)
    MyRecordSet.MoveNext
WEND

MyRecordSet.Close
Set MyRecordSet = Nothing

Para reproducir el problema, puedes guardar el código anterior en un archivo y mantener la extensión .vbs. Luego puedes ejecutarlo desde la línea de comandos. Es posible que necesites cambiar el valor de Source en la cadena de conexión para que coincida con tu entorno.

Una vez que ejecutemos el VB Script, obtendrás una ventana emergente con el nombre de la tabla. Si el perfilador se captura desde el principio, deberíamos ver sp_cursoropen, que puede indicar la consulta. Si la consulta ya está en ejecución, entonces podemos usar la siguiente consulta:

SELECT creation_time, cursor_id, c.session_id, c.properties, c.creation_time, c.is_open, SUBSTRING(st.TEXT, (c.statement_start_offset / 2) + 1, ((CASE c.statement_end_offset WHEN -1 THEN DATALENGTH(st.TEXT) ELSE c.statement_end_offset END - c.statement_start_offset) / 2) + 1) AS statement_text
FROM sys.Dm_exec_cursors(0) AS c
JOIN sys.dm_exec_sessions AS s ON c.session_id = s.session_id
CROSS APPLY sys.Dm_exec_sql_text(c.sql_handle) AS st

Aquí tienes una ejecución de ejemplo y como podemos ver, podemos obtener el texto de la consulta que ha abierto el cursor. Nota: El cursor_id es el mismo que vimos en sp_cursorfetch. Además, si capturamos el perfil completo, podemos ver la siguiente salida:

Moral de la historia: Si ves sp_cursorfetch o FETCH API_CURSOR0000000000000001, entonces captura el perfil completo desde el inicio de la consulta O usa sys.Dm_exec_cursors para obtener la consulta exacta.

Hazme saber si alguna vez has enfrentado este problema en tus entornos antes. ¿Cómo lo depuraste?

Click to rate this post!
[Total: 0 Average: 0]

Let's work together

Send us a message or book free introductory meeting with us using button below.