Databases 4 min de lectura 30 de agosto de 2025

Cómo ver registros afectados en SQL Profiler y optimizar consultas

Guía para rastrear consultas y mejorar el rendimiento en SQL Server.

Solución en "Dos Clics" (TL;DR)

Explicación sobre cómo capturar la cantidad de filas devueltas por consultas SELECT en SQL Server y estrategias para optimizar las búsquedas de texto.

Al trabajar con aplicaciones empresariales conectadas a SQL Server, una de las tareas más comunes pero esquivas es auditar con precisión qué está ocurriendo detrás de escena. Mientras que herramientas clásicas como SQL Profiler muestran con exactitud las consultas enviadas, carecen de una forma nativa de reportar la cantidad de registros devueltos por un simple SELECT, ya que funciones como @@ROWCOUNT solo aplican para operaciones DML como INSERT, UPDATE o DELETE.

El problema: Rastreo sin métricas de volumen

Durante el análisis de una aplicación desarrollada en VB.NET y ASP.NET conectada a SQL Server, me encontré rastreando consultas generadas automáticamente mediante un ORM (en este caso, LINQ to SQL). La traza en SQL Profiler mostraba sentencias idénticas a esta:

SQL Server Profiler / T-SQL
exec sp_executesql N'SELECT [t0].[idnumber], [t0].[student_idnumber], [t0].[student_code], [t0].[student_name], [t0].[status_id], [t0].[status], [t0].[motive_id], [t0].[motive], [t0].[term_id], [t0].[term], [t0].[date], [t0].[support], [t0].[observation], [t0].[usercreated], [t0].[timecreated], [t0].[usermodified], [t0].[timemodified] FROM [dbo].[vw_academic_statuses] AS [t0] WHERE ([t0].[student_code] LIKE @p0) OR ([t0].[student_name] LIKE @p1)',N'@p0 nvarchar(4000),@p1 varchar(8000)',@p0=N'%%',@p1='%%'

El inconveniente principal era que el Profiler estándar no revelaba cuántas filas devolvía la consulta al cargar la interfaz web inicial (donde los filtros llegaban vacíos como %%). Esto provocaba un consumo oculto y desmedido de recursos.

La solución: Capturando filas con Extended Events

Para solucionar la limitación del Profiler en consultas de solo lectura, recurrí a los Extended Events en SQL Server Management Studio (SSMS). Esta herramienta ofrece un rendimiento superior y una capacidad de auditoría mucho más detallada sin saturar el motor de base de datos.

Configuración de la sesión de Extended Events

Para capturar exactamente el volumen de datos devuelto por los comandos SELECT, configuré una nueva sesión seleccionando eventos clave como rpc_completed y sql_statement_completed. Dentro de las acciones globales disponibles en el asistente, incorporé campos esenciales para el diagnóstico:

  • sql_text: Para visualizar la consulta completa ejecutada.
  • num_response_rows / row_count: El parámetro crítico para identificar cuántas filas retornó la sentencia.
  • logical_reads: Para medir el impacto en memoria de la consulta.
  • client_hostname y database_name: Para contextualizar el origen y el destino.
SQL Server Management Studio (Resultados de Extended Events)
logical_reads   193
object_name     sp_executesql
result          OK
row_count       1384
sql_text        (@p0 nvarchar(4000),@p1 varchar(8000))SELECT [t0].[idnumber], ... FROM [dbo].[vw_academic_statuses] AS [t0] WHERE ...
Hallazgo crítico: El campo row_count arrojó un valor de 1384 registros devueltos en la carga inicial del formulario, simplemente porque los filtros dinámicos no se habían aplicado todavía y el ORM ejecutó la consulta completa sin restricciones. Un consumo totalmente innecesario para un simple parpadeo de interfaz web.

Estrategias de optimización: LINQ to SQL y MSSQL

Identificar el volumen de datos devuelto fue solo el primer paso; el objetivo real era frenar el consumo innecesario de recursos. Analizando el comportamiento de la aplicación, el formulario maneja filtros dinámicos (búsquedas por código de estudiante o nombres completos mediante métodos Contains en LINQ), los cuales se traducen en cláusulas LIKE '%texto%'.

1. Dónde aplicar el límite de registros (TOP / Take)

Una duda habitual es si se debe restringir el volumen de datos desde la aplicación (VB.NET) o directamente en el motor de base de datos (MSSQL). La respuesta definitiva es hacerlo siempre en MSSQL mediante TOP o OFFSET-FETCH (o Take() en LINQ).

Consejo Práctico: Limitar los registros directamente en la base de datos evita transferir miles de filas innecesarias a través de la red, ahorra memoria RAM en el servidor de aplicación y mejora drásticamente los tiempos de respuesta del cliente.

2. Análisis del uso de Contains en LINQ

Dado que los usuarios realizan búsquedas combinadas —por ejemplo, buscando el carnet exacto 2025-15-0023 o partes del nombre como Guillermo Antonelo Palacios Duarte—, el uso de Contains en LINQ está justificado para búsquedas parciales. Sin embargo, hay que tener en cuenta que un LIKE '%val%' impide que SQL Server aproveche eficientemente los índices tradicionales basados en B-Tree, desencadenando escaneos completos si la tabla crece demasiado.

Para mitigar esto en escenarios donde la consulta inicial carece de filtros, la implementación óptima en la capa de datos consiste en asegurar un límite por defecto (como Take(100)) mientras los campos de búsqueda se mantengan vacíos:

C# / VB.NET (LINQ to SQL)
var query = context.vw_academic_statuses.AsQueryable();

if (!string.IsNullOrEmpty(studentCode))
    query = query.Where(x => x.student_code.Contains(studentCode));

if (!string.IsNullOrEmpty(studentName))
    query = query.Where(x => x.student_name.Contains(studentName));

// Limitar resultados por defecto para evitar cargas masivas
var result = query.Take(100).ToList();
La historia detrás de la nota

Descubrir que una simple vista devolvía más de mil registros silenciosos en cada carga inicial me demostró la importancia de combinar Extended Events con un control estricto de paginación en el ORM.