Volver al Hub

LABORATORIO 02

Misión

Estrategia de Índices

Fase 2: Performance Tuning

La Batalla:
Seek vs Scan

El 80% de las consultas lentas se resuelven diseñando el índice correcto. En este lab, aprenderás a evitar los costosos Key Lookups usando índices "Covering".

1

El Problema de Negocio

Finanzas necesita un reporte en tiempo real: "Ingresos totales por cantidad de pasajeros". La consulta es simple, pero en la tabla de 1 millón de filas, bloquea el servidor.

Consulta de Negocio
SELECT PassengerCount, SUM(TotalAmount) 
FROM Trips 
GROUP BY PassengerCount;
2

Diagnóstico (Línea Base)

Antes de optimizar, debemos medir. Usamos STATISTICS IO para ver las "Lecturas Lógicas" (Páginas de 8KB leídas de memoria).

01_check_cost.sql
SET STATISTICS IO, TIME ON;

-- Ejecutamos sin índices Non-Clustered (solo tenemos el PK)
SELECT PassengerCount, SUM(TotalAmount) 
FROM Trips 
GROUP BY PassengerCount;
RESULTADO:
Table 'Trips'. Scan count 1, logical reads ~24,500.
CPU time = 1200 ms, Elapsed time = 1250 ms.

Análisis del Plan: Clustered Index Scan

Como no hay un índice específico para PassengerCount, SQL Server tuvo que leer TODAS las páginas de la tabla (las 24,500 páginas que vimos en el Lab 1) para agrupar los datos. Esto es O(N). Inaceptable.

3

La Solución: Covering Index

Muchos crearían un índice solo en PassengerCount. ¡ERROR!

Si el índice no tiene el TotalAmount, el motor encuentra las filas rápido pero debe saltar a la tabla base para buscar el dinero. Eso es un Key Lookup y para agregaciones masivas es peor que un Scan.

El Truco: INCLUDE Guardamos una copia de TotalAmount en las hojas del índice. Así el motor nunca toca la tabla principal.
graph TD Idx[Índice Non-Clustered] -->|Busca Pasajeros| Leaf[Hoja del Índice] Leaf -->|Lee Monto (INCLUDE)| CPU[Suma] Leaf -.->|NO NECESARIO| Table[(Tabla Base)] style Table fill:#fecaca,stroke:#ef4444,stroke-dasharray: 5 5 style Leaf fill:#bbf7d0,stroke:#22c55e
02_create_covering_index.sql
-- Creamos el índice perfecto para esta consulta
CREATE NONCLUSTERED INDEX IX_Pasajeros_Covering
ON Trips(PassengerCount)      -- Columna del GROUP BY / WHERE
INCLUDE (TotalAmount);        -- Columna del SELECT / SUM

-- Nota: TotalAmount NO es parte del árbol de búsqueda,
-- solo vive en las hojas como dato adjunto (payload).
4

La Prueba Final

Volvemos a ejecutar la misma consulta. El motor debería detectar automáticamente el nuevo índice y usarlo.

03_verify.sql
SELECT PassengerCount, SUM(TotalAmount) 
FROM Trips 
GROUP BY PassengerCount;
RESULTADO OPTIMIZADO:
Table 'Trips'. Scan count 1, logical reads ~140.
CPU time = 15 ms, Elapsed time = 15 ms.
Lecturas Lógicas
24,500 → 140
99.4% Menos I/O
Tiempo CPU
1200ms → 15ms
98% Más Rápido
Plan Usado
Index Seek (NonClustered)