Ir al contenido
wannacode

Consultas lentas

En resumen: Una consulta sigue lenta a pesar del índice cuando la columna está dentro de una función o de un LIKE, cuando pide todas las columnas, cuando un OR comprueba también una columna sin índice o cuando cuenta todo para responder «¿hay alguno?».

Problema

El índice existe y aun así es lento

El informe anual cuenta las visitas de 2026. Hay un índice sobre seen_at. Ejecuta la consulta y mira el tiempo:

SQLbase de datos: tienda

Y el plan:

SQLbase de datos: tienda

SCAN visits USING COVERING INDEX. La base de datos lee el índice porque es más pequeño que la tabla, pero lo lee entero: las 300 000 entradas. No hay búsqueda.

La idea

La columna tiene que estar sola

El índice está ordenado por seen_at. No está ordenado por substr(seen_at, 1, 4). Para comprobar la condición, la base de datos tiene que calcular la función para cada entrada, y para eso tiene que recorrerlas todas.

La misma condición se puede escribir con límites, de modo que la columna quede sola en un lado:

SQLbase de datos: tienda
SQLbase de datos: tienda

El resultado es el mismo, y el plan es SEARCH. Compara los tiempos de las dos consultas.

Con la aritmética pasa igual. customer_id + 0 = 1234 da lo mismo que customer_id = 1234, pero el índice no ayuda:

SQLbase de datos: tienda

La regla: no toques la columna en WHERE. La aritmética pasa al otro lado (price > 10 / 1.2, no price * 1.2 > 10), y una función sobre una fecha se convierte en un intervalo.

Pruébalo

Octubre

En el campo está la consulta del informe anual. El informe mensual quiere las visitas de octubre de 2026. Escribe la condición de modo que use el índice sobre seen_at.

SQLbase de datos: tienda

Míralo

LIKE y el índice

Parece que LIKE '2026-10%' debería usar el índice: se conoce el principio, como en una guía telefónica. Mira el plan:

SQLbase de datos: tienda

En SQLite es SCAN. La razón: LIKE en SQLite no distingue entre mayúsculas y minúsculas, mientras que el índice está ordenado distinguiéndolas. SQLite usa un índice para LIKE 'inicio%' solo bajo ciertas condiciones, por ejemplo si la columna o el índice son COLLATE NOCASE. Para las fechas, el camino seguro es el intervalo de la tarjeta anterior.

Un patrón que empieza por % no puede usar un índice normal en ninguna base de datos. El principio es desconocido, así que la búsqueda no tiene por dónde empezar:

SQLbase de datos: tienda
Más precisamente, para curiosos

La excepción a la regla de las funciones es un índice sobre una expresión: CREATE INDEX idx_visits_year ON visits(substr(seen_at, 1, 4)) vuelve a convertir WHERE substr(seen_at, 1, 4) = '2026' en un SEARCH. El índice solo ayuda a las consultas con la misma expresión.

En SQLite, GLOB '2026-10*' distingue entre mayúsculas y minúsculas y sí usa el índice (el plan es SEARCH). Lo mismo hace PRAGMA case_sensitive_like = ON, pero cambia el comportamiento de todos los LIKE de la conexión.

En PostgreSQL un índice normal ayuda con LIKE 'inicio%' solo con la colación C o si se creó con text_pattern_ops. Para buscar en medio de un texto hay tipos de índice aparte (la extensión pg_trgm). La búsqueda de texto completo está en el módulo sobre los tipos de bases de datos.

La idea

Solo las columnas que necesitas

El programa muestra en qué fechas visitaron los clientes del 1000 al 1100. Dos consultas con las mismas filas. Compara los tiempos:

SQLbase de datos: tienda
SQLbase de datos: tienda

La segunda es más rápida, porque customer_id y seen_at están en el índice (customer_id, seen_at), y este las cubre: la base de datos no abre la tabla. SELECT * quiere también product_id, así que para cada una de las 6 060 entradas la base de datos va además a la tabla. Pon en SELECT solo las columnas que el programa usa de verdad. Así también viaja menos información hacia el programa.

La idea

OR e IN

OR sobre la misma columna es lo mismo que IN. SQLite los ejecuta igual:

SQLbase de datos: tienda

La trampa es OR entre columnas distintas. No hay índice sobre product_id. Las filas con product_id = 4 pueden estar en cualquier lugar de la tabla, así que la base de datos lo lee todo, y el índice del cliente no ayuda:

SQLbase de datos: tienda

Si las dos columnas tienen índice, SQLite busca en cada una por separado y une los resultados (el plan dice MULTI-INDEX OR). Si a una mitad le falta el índice, sigue siendo SCAN.

La idea

«¿Hay alguno?», no «¿cuántos?»

La página del producto 13 muestra «Popular» si alguien lo ha visto. Contar encuentra las 12 000 visitas:

SQLbase de datos: tienda

La página solo necesita «sí» o «no». EXISTS se detiene en la primera fila que encuentra:

SQLbase de datos: tienda

No hay índice sobre product_id, pero EXISTS sigue siendo rápido, porque la primera visita del producto está cerca del principio de la tabla. Si no existe esa fila, EXISTS también lo leerá todo. Así que también aquí ayuda un índice. Otra forma es SELECT 1 FROM … WHERE … LIMIT 1: un resultado vacío significa «no».

Ejercicio

Dos tareas

1. En el campo está la consulta antigua de 2026. El cliente 1234 pregunta cuántas veces miró productos en 2025. Escríbela de modo que el plan sea SEARCH tanto por el cliente como por la fecha.

Las visitas del cliente 1234 en 2025, con SEARCH en las dos columnas:

SQLbase de datos: tienda

2. El botón «Ver otra vez» se muestra si el cliente 4321 ha visto algún producto a partir del 1 de octubre de 2026. Devuelve 1 o 0 en una columna seen.

¿Tiene el cliente 4321 visitas a partir del 2026-10-01?

SQLbase de datos: tienda

Test

Ponte a prueba

1. Hay un índice sobre seen_at. ¿Qué condición puede usarlo para una búsqueda?
2. ¿Por qué WHERE customer_id + 0 = 1234 no usa el índice sobre customer_id?
3. El programa necesita saber si el cliente ha visto el producto alguna vez. ¿Qué consulta es la mejor?
4. Hay un índice sobre customer_id y ninguno sobre product_id. ¿Cuál es el plan de WHERE customer_id = 1234 OR product_id = 4?

Creado por Ivo Gergov como parte de la campaña de Evo Tech por una educación accesible en programación ·