Aprende cómo los índices de base de datos reducen tiempos de consulta, cuándo ayudan (y cuándo perjudican) y pasos prácticos para diseñar, probar y mantener índices en aplicaciones reales.

Un índice de base de datos es una estructura de búsqueda separada que ayuda a la base de datos a encontrar filas más rápido. No es una segunda copia de tu tabla. Piénsalo como las páginas de índice de un libro: usas el índice para saltar cerca del lugar correcto y luego lees la página exacta (fila) que necesitas.
Sin un índice, la base de datos a menudo solo tiene una opción segura: leer muchas filas para comprobar cuáles coinciden con tu consulta. Eso puede estar bien cuando una tabla tiene unos pocos miles de filas. A medida que la tabla crece hasta millones de filas, “comprobar más filas” se convierte en más lecturas de disco, más presión de memoria y más trabajo de CPU: la misma consulta que antes parecía instantánea empieza a ralentizarse.
Los índices reducen la cantidad de datos que la base de datos debe inspeccionar para responder preguntas como “encuentra la orden con ID 123” o “obtén usuarios con este correo”. En lugar de escanear todo, la base de datos sigue una estructura compacta que estrecha la búsqueda rápidamente.
Pero la indexación no es una solución universal. Algunas consultas todavía necesitan procesar muchas filas (informes amplios, filtros de baja selectividad, agregaciones pesadas). Y los índices tienen costes reales: almacenamiento extra y escrituras más lentas, porque los inserts y updates también deben actualizar el índice.
Verás:
Cuando una base de datos ejecuta una consulta, tiene dos opciones generales: escanear toda la tabla fila por fila, o saltar directamente a las filas que coinciden. La mayoría de las ganancias de indexación vienen de evitar lecturas innecesarias.
Un escaneo completo de tabla hace exactamente lo que su nombre indica: la base de datos lee cada fila, comprueba si coincide con la condición WHERE y solo entonces devuelve resultados. Eso es aceptable para tablas pequeñas, pero se ralentiza de forma predecible a medida que la tabla crece: más filas significa más trabajo.
Usando un índice, la base de datos a menudo puede evitar leer la mayoría de las filas. En su lugar, consulta primero el índice (una estructura compacta construida para buscar) para encontrar dónde viven las filas coincidentes y luego lee solo esas filas específicas.
Piensa en un libro. Si quieres cada página que mencione “fotosíntesis”, podrías leer todo el libro de principio a fin (escaneo completo). O podrías usar el índice, saltar a las páginas listadas y leer solo esas secciones (búsqueda por índice). La segunda forma es más rápida porque te saltas casi todas las páginas.
Las bases de datos pasan mucho tiempo esperando lecturas, especialmente cuando los datos no están en memoria. Reducir la cantidad de filas (y páginas) que la base de datos tiene que tocar normalmente disminuye:
La indexación ayuda sobre todo cuando los datos son grandes y el patrón de consulta es selectivo (por ejemplo, obtener 20 filas coincidentes de 10 millones). Si tu consulta devuelve la mayoría de las filas, o la tabla es lo bastante pequeña para caber cómodamente en memoria, un escaneo completo puede ser igual de rápido o incluso más rápido.
Los índices funcionan porque organizan valores para que la base de datos pueda saltar cerca de lo que quieres en lugar de comprobar cada fila.
La estructura de índice más común en bases de datos SQL es el B-tree (a menudo escrito “B-tree” o “B+tree”). Conceptualmente:
Como está ordenado, un B-tree es excelente tanto para búsquedas por igualdad (WHERE email = ...) como para consultas por rango (WHERE created_at >= ... AND created_at < ...). La base de datos puede navegar hasta el vecindario correcto y luego escanear hacia adelante en orden.
Se dice que las búsquedas en B-tree son “logarítmicas”. Prácticamente, eso significa: cuando tu tabla crece de miles a millones de filas, el número de pasos para encontrar un valor crece lentamente, no proporcionalmente.
En vez de “el doble de datos significa el doble de trabajo”, es más bien “muchos más datos significan solo unos pocos pasos de navegación extra”, porque la base de datos sigue punteros a través de un pequeño número de niveles en el árbol.
Algunos motores también ofrecen índices hash. Pueden ser muy rápidos para comparaciones de igualdad exacta porque el valor se transforma en un hash y se usa para encontrar la entrada directamente.
La contraprestación: los índices hash normalmente no ayudan con rangos ni con escaneos ordenados, y la disponibilidad/comportamiento varía según la base de datos.
PostgreSQL, MySQL/InnoDB, SQL Server y otros almacenan y usan índices de forma distinta (tamaño de página, clustering, columnas incluidas, comprobaciones de visibilidad). Pero el concepto central se mantiene: los índices crean una estructura compacta y navegable que permite localizar filas coincidentes con mucho menos trabajo que escanear toda la tabla.
Los índices no aceleran “SQL” en general: aceleran patrones de acceso específicos. Cuando un índice coincide con cómo filtras, unes u ordenas, la base de datos puede saltar directamente a las filas relevantes en lugar de leer toda la tabla.
1) Filtros WHERE (especialmente en columnas selectivas)
Si tu consulta suele reducir una tabla grande a un pequeño conjunto de filas, un índice suele ser el primer lugar a revisar. Un ejemplo clásico es buscar un usuario por un identificador.
Sin un índice en users.email, la base de datos puede tener que escanear cada fila:
SELECT * FROM users WHERE email = '[email protected]';
Con un índice en email, puede localizar la(s) fila(s) coincidente(s) rápidamente y detenerse.
2) Claves de JOIN (foreign keys y columnas referenciadas)
Las joins son donde las “pequeñas ineficiencias” se convierten en grandes costes. Si unes orders.user_id con users.id, indexar las columnas de la unión (típicamente orders.user_id y la clave primaria users.id) ayuda a la base de datos a emparejar filas sin escaneos repetidos.
3) ORDER BY (cuando quieres resultados ya ordenados)
Ordenar es caro cuando la base de datos debe recoger muchas filas y ordenarlas después. Si con frecuencia ejecutas:
SELECT * FROM orders WHERE user_id = 42 ORDER BY created_at DESC;
un índice que combine user_id y la columna de orden puede permitir al motor leer las filas en el orden necesario en lugar de ordenar un gran conjunto intermedio.
4) GROUP BY (cuando agrupar coincide con un índice)
Agrupar puede beneficiarse cuando la base de datos puede leer datos en orden agrupado. No es una garantía, pero si agrupar por una columna que también se usa para filtrar (o que está naturalmente agrupada en el índice), el motor puede hacer menos trabajo.
Los índices B-tree son especialmente buenos para condiciones de rango: piensa en fechas, precios y consultas “entre”:
SELECT * FROM orders
WHERE created_at >= '2025-01-01' AND created_at < '2025-02-01';
Para paneles, informes y pantallas de “actividad reciente”, este patrón es omnipresente, y un índice en la columna de rango suele producir una mejora inmediata.
El tema es simple: los índices ayudan cuando reflejan cómo buscas y ordenas. Si tus consultas encajan con esos patrones, la base de datos puede hacer lecturas dirigidas en vez de amplios escaneos.
Un índice ayuda sobre todo cuando reduce drásticamente cuántas filas la base de datos tiene que tocar. Esa propiedad se llama selectividad.
La selectividad es básicamente: ¿cuántas filas coinciden con un valor dado? Una columna altamente selectiva tiene muchos valores distintos, por lo que cada búsqueda coincide con pocas filas.
email, user_id, order_number (a menudo único o casi único)is_active, is_deleted, status con pocos valores comunesCon alta selectividad, un índice puede saltar directamente a un pequeño conjunto de filas. Con baja selectividad, el índice apunta a un gran trozo de la tabla—la base de datos aún tiene que leer y filtrar mucho.
Considera una tabla con 10 millones de filas y una columna is_deleted donde el 98% son false. Un índice en is_deleted no ahorra mucho para:
SELECT * FROM orders WHERE is_deleted = false;
El conjunto de coincidencias sigue siendo casi toda la tabla. Usar el índice puede ser incluso más lento que un escaneo secuencial porque el motor hace trabajo extra saltando entre entradas del índice y páginas de la tabla.
Los planificadores estiman costes. Si un índice no reduce lo suficiente el trabajo—porque demasiadas filas coinciden, o porque la consulta necesita la mayor parte de las columnas—pueden elegir un escaneo completo.
La distribución de datos no es fija. Una columna status puede empezar distribuida equitativamente y luego derivar a un valor dominante. Si las estadísticas no se actualizan, el planificador puede tomar malas decisiones, y un índice que antes ayudaba puede dejar de compensar.
Los índices de una sola columna son un buen comienzo, pero muchas consultas reales filtran por una columna y ordenan o filtran por otra. Ahí brillan los índices compuestos (multicolumna): un solo índice puede cubrir varias partes de la consulta.
La mayoría de las bases de datos (especialmente con índices B-tree) solo pueden usar eficientemente un índice compuesto desde las columnas más a la izquierda en adelante. Piensa en el índice como ordenado primero por la columna A, luego dentro de esa por la columna B, y así sucesivamente.
Eso significa:
account_id y luego ordenan o filtran por created_atcreated_at (porque no es la columna más a la izquierda)Una carga común es “muéstrame los eventos más recientes para esta cuenta.” Este patrón:
SELECT id, created_at, type
FROM events
WHERE account_id = ?
ORDER BY created_at DESC
LIMIT 50;
suele beneficiarse enormemente de:
CREATE INDEX events_account_created_at
ON events (account_id, created_at);
La base de datos puede saltar directamente a la porción del índice de una cuenta y leer filas en orden temporal, en lugar de escanear y ordenar un conjunto grande.
Un índice de cobertura contiene todas las columnas que necesita la consulta, así que la base de datos puede devolver resultados desde el índice sin mirar las filas de la tabla (menos lecturas, menos I/O aleatorio).
Cuidado: añadir columnas extra puede hacer que el índice sea grande y caro.
Los índices compuestos muy amplios pueden ralentizar las escrituras y consumir mucho almacenamiento. Añádelos solo para consultas concretas de alto valor y verifica con un plan EXPLAIN y mediciones reales antes y después.
Los índices a menudo se describen como “velocidad gratis”, pero no lo son. Las estructuras de índices deben mantenerse cada vez que la tabla subyacente cambia y consumen recursos reales.
Cuando haces un INSERT de una nueva fila, la base de datos no solo escribe la fila: también inserta entradas correspondientes en cada índice de esa tabla. Lo mismo sucede con DELETE y muchos UPDATE.
Por eso “más índices” puede ralentizar notablemente cargas con muchas escrituras. Un UPDATE que toca una columna indexada puede ser especialmente caro: la base de datos puede tener que eliminar la entrada de índice vieja y añadir una nueva (y en algunos motores, esto puede desencadenar divisiones de página o reequilibrios internos). Si tu app hace muchas escrituras—eventos de órdenes, datos de sensores, logs de auditoría—indexarlo todo puede hacer que la base de datos se sienta lenta aunque las lecturas sean rápidas.
Cada índice ocupa espacio en disco. En tablas grandes, los índices pueden rivalizar (o incluso superar) el tamaño de la tabla, especialmente si tienes índices solapados.
También afecta la memoria. Las bases de datos dependen mucho del caching; si tu conjunto de trabajo incluye varios índices grandes, la caché debe mantener más páginas para seguir siendo rápida. Si no, verás más E/S de disco y un rendimiento menos predecible.
Indexar es elegir qué acelerar. Si tu carga es de lectura intensiva, más índices pueden merecer la pena. Si es de escritura intensiva, prioriza índices que soporten tus consultas más importantes y evita duplicados. Una regla útil: añade un índice solo cuando puedas nombrar la consulta a la que ayuda—y verifica que la ganancia de lectura compense el coste en escritura y mantenimiento.
Añadir un índice parece que debería ayudar—pero puedes (y debes) verificarlo. Las dos herramientas que lo concretan son el plan de consulta (EXPLAIN) y mediciones reales antes/después.
Ejecuta EXPLAIN (o EXPLAIN ANALYZE) en la consulta exacta que te importa.
EXPLAIN ANALYZE): si el plan estimó 100 filas pero realmente tocó 100.000, el optimizador se equivocó—a menudo porque las estadísticas están desactualizadas o el filtro es menos selectivo de lo esperado.ORDER BY, esa ordenación puede desaparecer, lo que supone una gran mejora.Haz benchmark de la consulta con los mismos parámetros, sobre datos representativos y captura tanto latencia como filas procesadas.
Cuidado con la caché: la primera ejecución puede ser más lenta porque los datos no están en memoria; ejecuciones repetidas pueden aparentar “arreglarse” sin índice. Para no engañarte, compara varias ejecuciones y céntrate en si el plan cambia (índice usado, menos filas leídas) además del tiempo.
Si EXPLAIN ANALYZE muestra menos filas tocadas y menos pasos costosos (como sorts), has demostrado que el índice ayuda—no solo lo has esperado.
Puedes añadir el “índice correcto” y aun así no ver mejora si la consulta está escrita de forma que impide su uso. Estos problemas suelen ser sutiles, porque la consulta sigue devolviendo resultados correctos, pero con un plan más lento.
1) Wildcards al principio
Cuando escribes:
WHERE name LIKE '%term'
la base de datos no puede usar un índice B-tree normal para saltar al punto de inicio, porque no sabe dónde comienza “%term” en el orden. Suele recurrir a escanear muchas filas.
Alternativas:
WHERE name LIKE 'term%'.2) Funciones sobre columnas indexadas
Esto parece inofensivo:
WHERE LOWER(email) = '[email protected]'
Pero LOWER(email) cambia la expresión, por lo que el índice sobre email no puede usarse directamente.
Alternativas:
WHERE email = ....LOWER(email).Cast implícitos de tipo: Comparar distintos tipos puede forzar al motor a castear un lado, lo que puede deshabilitar el índice. Ejemplo: comparar una columna integer con una literal de texto.
Intercalaciones/collations mismatched: Si la comparación usa una colación distinta a la del índice (común en columnas de texto entre locales), el optimizador puede evitar el índice.
LIKE '%x')?LOWER(col), DATE(col), CAST(col)) ?EXPLAIN para confirmar la elección del optimizador?Los índices no son “ponlo y olvídalo”. Con el tiempo, los datos cambian, los patrones de consulta se desplazan y la forma física de tablas e índices deriva. Un índice bien elegido puede volverse menos efectivo—o incluso dañino—si no lo mantienes.
La mayoría de bases de datos usan un planificador para elegir cómo ejecutar una consulta: qué índice usar, orden de joins y si merece la pena una búsqueda por índice. Para decidir, el planificador usa estadísticas—resúmenes sobre distribuciones de valores, conteo de filas y sesgos.
Cuando las estadísticas están obsoletas, las estimaciones de filas del plan pueden ser muy erróneas. Eso conduce a malas elecciones de plan, como escoger un índice que devuelve muchas más filas de las previstas o saltarse un índice que habría sido más rápido.
Arreglo rutinario: programa actualizaciones regulares de estadísticas (a menudo ANALYZE o similar). Después de grandes cargas de datos, borrados masivos o churn significativo, refresca las estadísticas antes.
A medida que se insertan, actualizan y eliminan filas, los índices pueden acumular bloat (páginas extra que ya no contienen datos útiles) y fragmentación (datos dispersos que aumentan la E/S). El resultado son índices más grandes y lecturas más lentas, especialmente para consultas por rango.
Arreglo rutinario: reconstruye o reorganiza periódicamente índices muy usados cuando hayan crecido desproporcionadamente o el rendimiento baje. Las herramientas e impactos varían por base de datos; trátalo como una operación medida, no como una regla general sin más.
Configura monitorización para:
Ese bucle de retroalimentación te ayuda a detectar cuándo se necesita mantención—y cuándo ajustar o eliminar un índice. Para más sobre validar mejoras, consulta /blog/how-to-prove-an-index-helps-explain-and-measurements.
Añadir un índice debe ser un cambio deliberado, no una conjetura. Un flujo ligero te mantiene centrado en ganancias medibles y evita la “proliferación de índices”.
Empieza con evidencia: logs de consultas lentas, trazas APM o informes de usuarios. Escoge una consulta que sea tanto lenta como frecuente—un informe raro de 10 s importa menos que una búsqueda común de 200 ms.
Captura el SQL exacto y el patrón de parámetros (por ejemplo: WHERE user_id = ? AND status = ? ORDER BY created_at DESC LIMIT 50). Pequeñas diferencias cambian qué índice ayuda.
Registra la latencia actual (p50/p95), filas escaneadas y consumo de CPU/E/S. Guarda el plan actual (EXPLAIN / EXPLAIN ANALYZE) para comparar después.
Elige columnas que coincidan con cómo filtra y ordena la consulta. Prefiere el índice mínimo que haga que el plan deje de escanear rangos enormes.
Prueba en staging con volumen de datos similar a producción. Los índices pueden lucir bien en datasets pequeños y decepcionar a escala.
En tablas grandes, usa opciones online cuando estén disponibles (por ejemplo, PostgreSQL CREATE INDEX CONCURRENTLY). Programa cambios en horarios de menor tráfico si la base de datos puede bloquear escrituras.
Re-ejecuta la misma consulta y compara:
Si el índice aumenta el coste de las escrituras o causa bloat, elimínalo limpiamente (por ejemplo, DROP INDEX CONCURRENTLY donde esté disponible). Mantén la migración reversible.
En la migración o notas de esquema, escribe qué consulta sirve el índice y qué métrica mejoró. El futuro tú (o un compañero) sabrá por qué existe y cuándo es seguro eliminarlo.
Si estás construyendo un servicio nuevo y quieres evitar la “proliferación de índices” desde el inicio, Koder.ai puede ayudarte a iterar más rápido en el ciclo completo anterior: generar una app React + Go + PostgreSQL desde chat, ajustar migraciones de esquema/índices según los requisitos y luego exportar el código cuando estés listo para tomar el control manualmente. En la práctica, eso facilita ir de “este endpoint es lento” a “aquí está el plan EXPLAIN, el índice mínimo y una migración reversible” sin esperar toda una canalización tradicional.
Los índices son una palanca poderosa, pero no un botón mágico. A veces la parte lenta de una petición ocurre después de que la base de datos encuentre las filas correctas, o el patrón de consulta hace que indexar no sea la primera solución.
Si tu consulta ya usa un buen índice pero aún va lenta, busca estos culpables comunes:
OFFSET 999000 puede ser lento incluso con índices. Prefiere paginación por keyset (por ejemplo, “dame filas después del último id/timestamp visto”).SELECT *) o devolver decenas de miles de registros puede convertirse en cuello de botella en red, serialización JSON o procesamiento en la app.LIMIT sensato y pagina intencionalmente.Si quieres un diagnóstico más profundo de cuellos de botella, combínalo con el flujo en /blog/how-to-prove-an-index-helps.
No adivines. Mide dónde se gasta el tiempo (ejecución en BD vs. filas devueltas vs. código de la app). Si la base de datos es rápida pero la API es lenta, más índices no ayudarán.
Un índice de base de datos es una estructura de datos separada (a menudo un B-tree) que guarda valores seleccionados de columnas en una forma ordenada y buscable con punteros de vuelta a las filas de la tabla. La base de datos lo usa para evitar leer la mayor parte de la tabla cuando responde consultas selectivas.
No es una copia completa de la tabla, pero sí duplica algunos datos de columnas más metadatos, por lo que consume espacio adicional.
Sin un índice, la base de datos puede tener que hacer un escaneo completo de la tabla: leer muchas (o todas) las filas y comprobar cada una contra tu cláusula WHERE.
Con un índice, normalmente puede saltar directamente a las ubicaciones de las filas coincidentes y leer solo esas filas, reduciendo E/S de disco, trabajo de CPU para filtrar y presión sobre la caché.
Un índice B-tree mantiene los valores ordenados y organizados en páginas que apuntan a otras páginas, de modo que la base de datos puede navegar rápidamente al “vecindario” correcto de valores.
Por eso los B-trees funcionan bien tanto para:
WHERE email = ...)WHERE created_at >= ... AND created_at < ...)Los índices hash pueden ser muy rápidos para igualdad exacta (=) porque convierten un valor en un hash y saltan a su cubeta.
Contras:
En muchas cargas reales, los B-trees son la opción por defecto porque cubren más patrones de consulta.
Los índices suelen ayudar más para:
WHERE selectivos (pocos resultados)JOIN (claves foráneas y columnas referenciadas)ORDER BY que coincide con el orden del índice (puede evitar la ordenación)La selectividad es “cuántas filas coinciden con un valor”. Los índices resultan cuando un predicado reduce mucho la tabla a un conjunto pequeño.
Columnas de baja selectividad (por ejemplo is_deleted, is_active, enums con pocos valores) suelen coincidir con una gran parte de la tabla. En esos casos, usar el índice puede ser más lento que un escaneo porque el motor aún tiene que obtener y filtrar muchas filas.
Porque el optimizador estima que usarlo no reducirá suficientemente el trabajo.
Razones comunes:
En la mayoría de implementaciones B-tree, el índice está ordenado por la primera columna, luego dentro de esa por la segunda, etc. Por eso la base de datos puede usar el índice de forma eficiente empezando por las columnas más a la izquierda.
Ejemplo:
(account_id, created_at) es excelente para WHERE account_id = ? y luego filtrar/ordenar por .Un índice de cobertura incluye todas las columnas que necesita la consulta, de manera que la base de datos puede devolver resultados desde el índice sin leer las filas de la tabla.
Beneficios:
Costes:
Usa índices de cobertura para consultas específicas de alto valor, no “por si acaso”.
Comprueba dos cosas:
EXPLAIN / EXPLAIN ANALYZE y confirma que el plan cambió (por ejemplo, Seq Scan → Index Scan/Seek, menos filas leídas, desapareció la ordenación).GROUP BYSi una consulta devuelve una gran fracción de la tabla, el beneficio suele ser pequeño.
created_atcreated_at (no es la columna más a la izquierda).También vigila el impacto en las escrituras, porque los índices nuevos pueden empeorar INSERT/UPDATE/DELETE.