Un índice de base de datos es una estructura de búsqueda ordenada que permite al motor encontrar las filas directamente, en vez de escanear toda la tabla. La analogía del libro es exacta: nadie encuentra “idempotencia” en un libro de 900 páginas leyéndolo — consulta el índice y salta a la página. Las bases de datos toman la misma decisión en cada query, y que puedan saltar o no es la diferencia más común entre una query de 5 milisegundos y una de 5 segundos.
Puntos clave
| Pregunta | Respuesta |
|---|---|
| Qué es | Una estructura lateral ordenada: valores + punteros, como el índice de un libro |
| La ganancia | Búsqueda logarítmica en vez de recorrido lineal — decisivo a escala |
| El impuesto | Cada escritura actualiza cada índice; el almacenamiento crece |
| Qué indexar | Columnas de filtro, de join/puntero y de ordenamiento — si son selectivas |
| El diagnóstico | EXPLAIN: un table scan en una tabla grande es la señal |
El índice, creado y sentido
-- El caballo de batalla: un índice compuesto con la forma de la query caliente
CREATE INDEX orders_pending ON orders (status, created_at);
-- Especializaciones de la misma idea:
CREATE UNIQUE INDEX users_email ON users (email); -- constraint + velocidad
CREATE INDEX unshipped ON orders (created_at)
WHERE shipped = false; -- parcial: solo el subconjunto caliente
-- El antes/después, según el plan:
EXPLAIN SELECT * FROM orders WHERE status = 'pending' AND created_at > now() - interval '1 day';
-- antes: Seq Scan on orders (filas examinadas: 4.812.309)
-- después: Index Scan using orders_pending (filas examinadas: 1.214)
El código de aplicación se topa con la misma física a través de la forma de la query — esta query es la especificación del índice (status, createdAt), sea cual sea la superficie que la escribe:
// JavaScript / Node.js — Back4app JS SDK
// The query shape tells you the index: (status, createdAt)
const query = new Parse.Query('Order');
query.equalTo('status', 'pending'); // equality first…
query.greaterThan('createdAt', since); // …then the range
query.descending('createdAt'); // …sorted by the same column
const backlog = await query.find();
// Indexed: milliseconds at any size. Unindexed: a full collection scan. // Flutter / Dart — Back4app Flutter SDK
// The query shape tells you the index: (status, createdAt)
final query = QueryBuilder<ParseObject>(ParseObject('Order'))
..whereEqualTo('status', 'pending') // equality first…
..whereGreaterThan('createdAt', since) // …then the range
..orderByDescending('createdAt'); // …sorted by the same column
final response = await query.query();
// Indexed: milliseconds at any size. Unindexed: a full collection scan. // iOS / Swift — Back4app Swift SDK
// The query shape tells you the index: (status, createdAt)
let query = Order.query("status" == "pending", "createdAt" > since)
.order([.descending("createdAt")])
query.find { result in
if case .success(let backlog) = result { render(backlog) }
}
// Indexed: milliseconds at any size. Unindexed: a full collection scan. // Android / Kotlin — Back4app Android SDK
// The query shape tells you the index: (status, createdAt)
val query = ParseQuery.getQuery<ParseObject>("Order")
query.whereEqualTo("status", "pending") // equality first…
query.whereGreaterThan("createdAt", since) // …then the range
query.orderByDescending("createdAt") // …sorted by the same column
query.findInBackground { backlog, e -> if (e == null) render(backlog) }
// Indexed: milliseconds at any size. Unindexed: a full collection scan. Cómo funciona el salto
La estructura por defecto en todas partes es la B-tree: poco profunda, ordenada, autobalanceada — tres o cuatro niveles cubren cientos de millones de entradas, y sus hojas están enlazadas, razón por la que una misma estructura sirve igualdad, rangos y ORDER BY por igual. Esa versatilidad es el motivo de que “ante la duda, B-tree” sea el consejo permanente, con las alternativas actuando como especialistas.
B-tree vs. hash vs. los especialistas
¿Qué tipo de índice deberías usar?
| Tipo | Sirve | Úsalo cuando |
|---|---|---|
| B-tree (default) | =, <, >, rangos, ordenamiento, prefijos | Casi siempre — el generalista |
| Hash | Solo igualdad, O(1) | Lookups de coincidencia exacta, nada más |
| Compuesto | Multicolumna, prefijo izquierdo | La query caliente filtra por varias columnas |
| Único | B-tree + sin duplicados | Constraint e índice en uno |
| Parcial | Una porción filtrada de filas | Subconjuntos calientes: sin enviar, sin leer, activos |
| Covering | Query respondida solo con el índice | Queries de lectura intensiva con lista estable de columnas |
| Full-text (invertido) | Palabras → documentos | Cajas de búsqueda |
| Geoespacial | Puntos, regiones, distancia | Queries de “cerca de mí” |
Dos reglas cargan la fila del compuesto: la regla del prefijo izquierdo — un índice sobre (a, b, c) sirve a, (a, b), (a, b, c), nunca b sola — e igualdad primero, rango/ordenamiento al final en el orden de las columnas (el mnemónico ESR en la indexación de bases de documentos, donde las mismas B-trees hacen el mismo trabajo).
Cuándo indexar — y cuándo no
Indexa: columnas en filtros WHERE frecuentes; claves de join y campos de puntero (algunos motores nunca indexan las foreign keys automáticamente — un clásico table scan silencioso); columnas de ORDER BY en rutas calientes; y siempre con la selectividad en mente — una columna de email (millones de valores distintos) se gana su lugar, un flag de estado (tres valores) por sí solo en general no, aunque brilla liderando un compuesto con un rango detrás. No indexes: tablas pequeñas que el motor escanea más rápido de lo que busca; tablas calientes de escritura más allá de los pocos índices esenciales; columnas ya servidas por el prefijo izquierdo de un índice existente; y cualquier cosa para la que no puedas nombrar una query — un índice sin query es puro impuesto de escritura. El ciclo de auditoría: corre EXPLAIN sobre las queries lentas buscando scans, revisa las estadísticas de uso para hallar índices muertos, agrega y elimina en consecuencia.
Casos de uso comunes
- La query de la lista caliente. Filtros de estado + fecha detrás de cada dashboard y feed — el terreno natural del índice compuesto.
- Campos de login y lookup. Email, username, IDs externos — índices únicos haciendo constraint y velocidad a la vez.
- Rutas de join y puntero. Cada foreign key y cada puntero que tus queries recorren; el cómplice silencioso del problema N+1 es uno de ellos sin índice.
- Filtros multi-tenant. Compuestos liderados por el tenant — la regla de la arquitectura multi-tenant de que todo índice empieza con
tenant_id. - Búsqueda y geo. Índices invertidos y espaciales impulsando las queries que las B-trees no pueden.
¿Deberías agregar ese índice? Matriz de decisión
| Agrégalo cuando… | Sáltatelo cuando… |
|---|---|
| Una query frecuente filtra u ordena por esa columna | Ninguna query de producción la usa |
| EXPLAIN muestra scans en una tabla que crece | La tabla es pequeña y seguirá pequeña |
| La columna es selectiva (muchos valores distintos) | El prefijo de un índice existente ya la cubre |
| Es clave de join o campo de puntero | La tabla está dominada por escrituras y la lectura es rara |
| Una regla de unicidad hay que imponerla de todos modos | Estás adivinando — mide primero |
La disciplina en una frase: los índices se crean en respuesta a queries, se revisan contra el uso real y se eliminan sin sentimentalismo.
Limitaciones y trade-offs
- Las escrituras pagan por las lecturas. Cada índice es otra estructura que cada escritura debe mantener — las cargas masivas corren notoriamente más rápido con los índices eliminados y reconstruidos después.
- El almacenamiento es real. Los índices comúnmente rivalizan con el tamaño de la propia tabla; los covering indexes, en especial, cambian disco por velocidad.
- Decide el optimizador, no tú. Un índice de baja selectividad puede ser ignorado con razón; estadísticas desactualizadas pueden ignorar sin razón uno bueno — el plan es la verdad.
- Los errores de orden neutralizan los compuestos.
(created_at, status)y(status, created_at)son herramientas distintas; la regla del prefijo izquierdo no perdona nada. - El índice no arregla la query. Wildcards al inicio, funciones sobre columnas y loops N+1 derrotan la indexación desde arriba — la forma de la query va primero.
Índices en Back4app
Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. La gestión de índices vive junto al schema en el dashboard: crea, revisa y elimina índices por clase — de campo único o compuestos — sobre el almacenamiento respaldado por MongoDB que tus queries de SDK realmente consultan, siguiendo la misma lógica de B-tree y ESR de arriba. La query de las pestañas de código y el índice que la sirve se diseñan en el mismo lugar — exactamente donde la disciplina de “indexa lo que consultas” quiere vivir.
Preguntas frecuentes
¿Qué es un índice de base de datos en términos simples?
Una estructura separada y ordenada — normalmente una B-tree — que guarda valores de columna más punteros a sus filas, exactamente como el índice de un libro guarda términos y números de página. El motor busca el valor en la pequeña estructura ordenada y salta directo a las filas que coinciden, en vez de leer la tabla entera con la esperanza de encontrarlas.
¿Cuánto más rápida es una query indexada?
La diferencia entre trabajo logarítmico y lineal: un lookup en una B-tree toca un puñado de páginas, tenga la tabla diez mil filas o cien millones, mientras que un table scan lo lee todo. En tamaños pequeños ambos se sienten instantáneos — por eso los índices faltantes se esconden en desarrollo y detonan en producción, donde la misma query pasa a examinar millones de filas.
¿Los índices hacen más lentas las escrituras?
Sí — ese es el impuesto. Cada insert, update o delete sobre una columna indexada debe actualizar también cada índice que la referencia: seis índices en una tabla significan hasta seis actualizaciones extra de estructura por escritura, más el almacenamiento que ocupan. Los índices son una compra de velocidad de lectura pagada en velocidad de escritura y disco — los deliberados se la ganan, los olvidados solo te cobran.
¿Qué columnas deberían indexarse?
Las columnas que tus queries realmente usan: filtros (WHERE), claves de join — incluidas las foreign keys y los campos de puntero, que algunos motores no indexan automáticamente — y columnas de ordenamiento (ORDER BY). Suma la prueba de selectividad: una columna de email que distingue millones de filas se gana su índice; un flag booleano que parte la tabla a la mitad, en general, no.
¿En qué orden van las columnas de un índice compuesto?
Igualdad primero, rango y ordenamiento después — y recuerda la regla del prefijo izquierdo: un índice sobre (a, b, c) sirve a queries que filtran por a, por a y b, o por las tres, pero nunca por b sola. La formulación de la misma regla en las bases de documentos es ESR: Equality, Sort, Range. El orden de las columnas es la diferencia entre que un índice compuesto funcione y que apenas exista.
¿Cómo encuentro índices faltantes o sin uso?
Para los faltantes: corre el plan de la query — EXPLAIN — y busca table scans en tablas grandes; la columna de filtro de una query lenta y frecuente es la candidata. Para los sin uso: todo motor registra estadísticas de uso de índices, y un índice que ninguna query toca hace meses es puro impuesto de escritura — elimínalo. El plan y las estadísticas, juntos, son la metodología completa.
¿Qué son los índices únicos, parciales y covering?
Especializaciones de la misma estructura: un índice único impone la regla de no duplicados como constraint mientras acelera lookups; un índice parcial cubre solo las filas que cumplen una condición — pequeño y rápido para subconjuntos calientes, como pedidos sin enviar; un covering index contiene todas las columnas que la query necesita, y deja al motor responder solo con el índice, sin visitar la tabla.
¿La indexación funciona igual en bases de documentos?
Conceptualmente idéntica — B-trees sobre valores de campos — con los mismos trade-offs y la misma lógica de prefijo izquierdo bajo la regla práctica ESR. Las bases de documentos agregan índices multikey sobre campos de array y variantes geoespaciales, y la regla operativa sobrevive a la traducción: indexa lo que consultas, en especial los campos de puntero que los joins y los includes recorren.