Saltar al contenido
indices.sql · devschool

Índices en SQL

Lección 19 de 22 · 10 min de lectura · Actualizado el

En esta lección
  1. La analogía del libro
  2. Crear un índice: CREATE INDEX
  3. Índices que se crean solos
  4. Índices compuestos y el orden de las columnas
  5. Comprobar si se usa un índice: EXPLAIN
  6. Cuándo ayuda un índice y cuándo no
  7. El precio de los índices
  8. Índices en claves foráneas
  9. Borrar un índice: DROP INDEX
  10. Errores frecuentes
  11. Resumen

Cuando una tabla tiene cinco filas, cualquier consulta es instantánea. Cuando tiene cinco millones, la misma consulta puede tardar segundos… o milisegundos, según haya o no un índice. En esta lección aprenderás qué es un índice, cómo se crea, cuándo ayuda y cuándo estorba, y cómo comprobar si la base de datos lo está usando.

La analogía del libro

Piensa en un libro de cocina de 600 páginas. Quieres la receta de las croquetas. Tienes dos opciones:

  1. Pasar las páginas una a una hasta encontrarla. Si está al final, lees el libro entero.
  2. Ir al índice alfabético del final, buscar “Croquetas”, ver “página 412” e ir directo.

Un índice en una base de datos es exactamente lo segundo: una estructura aparte, ordenada por una o varias columnas, que dice en qué lugar de la tabla está cada fila. Sin índice, la base de datos tiene que recorrer la tabla entera (a esto se le llama escaneo completo o full table scan). Con índice, salta directamente a las filas que buscas.

Internamente, casi todos los sistemas guardan los índices como un árbol B (B-tree), que permite encontrar un valor entre millones con muy pocos pasos. No necesitas saber cómo funciona por dentro; basta con saber que está ordenado.

Crear un índice: CREATE INDEX

En la tienda del curso, una consulta muy habitual es “los pedidos de un cliente”:

SELECT * FROM pedidos WHERE cliente_id = 1;

Para que vaya rápida con muchos pedidos, se crea un índice sobre cliente_id:

CREATE INDEX idx_pedidos_cliente ON pedidos (cliente_id);
  • idx_pedidos_cliente es el nombre del índice. Es costumbre empezar por idx_ y poner la tabla y la columna.
  • ON pedidos (cliente_id) indica la tabla y la columna.

La consulta no cambia. Sigues escribiendo el mismo SELECT y obtienes el mismo resultado:

idcliente_idfechaimporte
112026-09-0145.00
312026-09-1012.99

Es la base de datos quien decide, por su cuenta, si le conviene usar el índice. Por eso los índices no cambian nunca los resultados; solo la velocidad.

Índices únicos

Un UNIQUE INDEX hace dos cosas: acelera las búsquedas y además impide valores repetidos:

CREATE UNIQUE INDEX idx_clientes_nombre ON clientes (nombre);

INSERT INTO clientes VALUES (6, 'Ana García', 'Bilbao', 50);
-- Error: ya existe un cliente con ese nombre

En la práctica, para impedir repetidos se suele usar la restricción UNIQUE al crear la tabla (lo ves en claves y restricciones). Por dentro es lo mismo: un índice único.

Índices que se crean solos

No siempre tienes que crear los índices a mano. La base de datos crea uno automáticamente para:

  • La clave primaria (PRIMARY KEY). Por eso buscar por id siempre es rápido.
  • Cada columna con restricción UNIQUE, porque para comprobar que no hay repetidos necesita buscar rápido.

En MySQL, el índice de la clave primaria se llama PRIMARY. Puedes ver los índices de una tabla con:

SHOW INDEX FROM pedidos;

Devuelve una fila por cada columna de cada índice, con datos como Key_name (nombre del índice), Column_name y Non_unique (0 si es único, 1 si admite repetidos).

Nota: SHOW INDEX es propio de MySQL. En PostgreSQL, el cliente psql los muestra con \d pedidos; en SQLite, con PRAGMA index_list(pedidos);.

Índices compuestos y el orden de las columnas

Un índice puede tener varias columnas. Imagina que buscas mucho por ciudad y edad a la vez:

CREATE INDEX idx_clientes_ciudad_edad ON clientes (ciudad, edad);

Este índice está ordenado primero por ciudad y, dentro de cada ciudad, por edad. Es como una guía telefónica, ordenada por apellido y luego por nombre: puedes buscar rápido a “todos los García” o a “García, Ana”, pero no a “todas las Ana”, porque las Ana están repartidas por toda la guía.

Esto se conoce como la regla del prefijo izquierdo: el índice solo sirve si la consulta usa sus columnas empezando por la izquierda.

Consulta¿Usa el índice (ciudad, edad)?
WHERE ciudad = 'Madrid'Sí, usa la primera columna
WHERE ciudad = 'Madrid' AND edad > 18Sí, usa las dos
WHERE edad > 18No: falta la primera columna
ORDER BY ciudad, edadSí, puede leer las filas ya ordenadas

Por eso el orden importa: (ciudad, edad) y (edad, ciudad) son índices distintos. Como regla práctica, pon primero la columna por la que filtras con = y que usas en más consultas.

Comprobar si se usa un índice: EXPLAIN

Para saber qué va a hacer la base de datos con una consulta, pon EXPLAIN delante. No ejecuta la consulta: te enseña su plan.

En SQLite se escribe EXPLAIN QUERY PLAN. Antes de crear el índice sobre cliente_id:

EXPLAIN QUERY PLAN SELECT * FROM pedidos WHERE cliente_id = 1;
QUERY PLAN
`--SCAN pedidos

SCAN significa que recorre la tabla entera. Después de crear idx_pedidos_cliente:

QUERY PLAN
`--SEARCH pedidos USING INDEX idx_pedidos_cliente (cliente_id=?)

SEARCH ... USING INDEX significa que salta directamente a las filas usando el índice. Con el índice compuesto del apartado anterior puedes comprobar la regla del prefijo izquierdo:

EXPLAIN QUERY PLAN SELECT * FROM clientes WHERE ciudad = 'Madrid' AND edad > 18;
-- SEARCH clientes USING INDEX idx_clientes_ciudad_edad (ciudad=? AND edad>?)

EXPLAIN QUERY PLAN SELECT * FROM clientes WHERE edad > 18;
-- SCAN clientes

En MySQL se escribe solo EXPLAIN y la salida es una tabla con muchas columnas. Las que más te interesan son type, key y rows. Simplificada, se parece a esto:

typekeyrows
ALLNULL5
refidx_pedidos_cliente2
  • type = ALL es el escaneo completo (lo que quieres evitar en tablas grandes).
  • key es el índice que ha elegido (NULL si ninguno).
  • rows es cuántas filas calcula que tendrá que leer. Es una estimación, no un dato exacto.

Consejo: MySQL 8 y PostgreSQL tienen también EXPLAIN ANALYZE, que sí ejecuta la consulta y muestra los tiempos reales. Es la mejor forma de saber si un índice merece la pena.

Cuándo ayuda un índice y cuándo no

Un índice ayuda en columnas que aparecen a menudo en:

  • WHERE (WHERE cliente_id = 1, WHERE fecha >= '2026-09-01').
  • Condiciones de JOIN (ON p.cliente_id = c.id).
  • ORDER BY y GROUP BY sobre tablas grandes.

Y no ayuda, o ayuda muy poco, en estos casos:

  • Tablas pequeñas. Con unos cientos de filas, leer la tabla entera es tan rápido que la base de datos ni mira el índice.
  • Columnas con pocos valores distintos. Una columna activo que solo vale sí o no: el índice te lleva a la mitad de la tabla, así que da igual.
  • LIKE que empieza por comodín. WHERE nombre LIKE '%rtín' no puede usar el índice, igual que no puedes buscar en la guía telefónica “apellidos que terminan en ez”. En cambio, LIKE 'Mar%' sí puede usarlo en MySQL, porque conoce el principio.
  • Funciones sobre la columna. WHERE UPPER(ciudad) = 'MADRID' no usa el índice de ciudad, porque el índice guarda Madrid, no MADRID. Escribe la condición sin transformar la columna siempre que puedas.

El precio de los índices

Si los índices aceleran tanto, ¿por qué no crear uno en cada columna? Porque no son gratis:

  • Ocupan espacio. Cada índice es una estructura aparte que se guarda en disco.
  • Hacen más lentas las escrituras. Cada INSERT, UPDATE o DELETE tiene que actualizar la tabla y todos sus índices. Una tabla con diez índices hace once escrituras por cada fila nueva.

La idea es crear índices para las consultas que de verdad se hacen a menudo, no “por si acaso”. En una tabla que se lee mucho y se escribe poco (un catálogo de productos) compensan más índices. En una que recibe miles de escrituras por segundo (un registro de visitas), conviene tener pocos.

Índices en claves foráneas

Las columnas de clave foránea, como pedidos.cliente_id, casi siempre merecen un índice:

  • Se usan en casi todos los JOIN entre las dos tablas.
  • Cuando borras o cambias un cliente, la base de datos tiene que buscar sus pedidos para comprobar la restricción. Sin índice, recorrería toda la tabla pedidos cada vez.

Nota: MySQL (con InnoDB) crea automáticamente un índice en la columna de la clave foránea si no existe ya. PostgreSQL y SQLite no lo hacen: tienes que crearlo tú con CREATE INDEX.

Borrar un índice: DROP INDEX

Si un índice ya no se usa, bórralo para ahorrar espacio y acelerar las escrituras. Los datos de la tabla no se tocan:

-- MySQL
DROP INDEX idx_clientes_nombre ON clientes;

-- PostgreSQL y SQLite
DROP INDEX idx_clientes_nombre;

Nota: en MySQL también puedes crear y borrar índices con ALTER TABLE: ALTER TABLE clientes ADD INDEX idx_ciudad (ciudad);. Además, MySQL no admite CREATE INDEX IF NOT EXISTS; PostgreSQL, SQLite y MariaDB sí.

Errores frecuentes

  • Crear índices en todas las columnas. Llenas el disco y ralentizas cada escritura para acelerar consultas que quizá nadie hace.
  • Olvidar el orden en los compuestos. Un índice (ciudad, edad) no sirve para WHERE edad = 30.
  • Crear un índice que ya existe. La clave primaria y las columnas UNIQUE ya tienen el suyo. Un CREATE INDEX sobre id es un índice duplicado.
  • Envolver la columna en una función (YEAR(fecha) = 2026). Mejor fecha >= '2026-01-01' AND fecha < '2027-01-01', que sí puede usar el índice.
  • No medir. Antes y después de crear un índice, mira el plan con EXPLAIN. Así sabes si la base de datos lo usa.

Resumen

InstrucciónQué hace
CREATE INDEX idx ON t (col)Crea un índice sobre una columna
CREATE UNIQUE INDEX idx ON t (col)Índice que además impide repetidos
CREATE INDEX idx ON t (a, b)Índice compuesto: sirve para a o para a y b
EXPLAIN SELECT ...Muestra el plan (en SQLite, EXPLAIN QUERY PLAN)
SHOW INDEX FROM tLista los índices de una tabla (MySQL)
DROP INDEX idx ON tBorra un índice (MySQL; en otros, sin ON t)
  • Un índice es una estructura ordenada que evita recorrer la tabla entera.
  • Acelera las lecturas y frena las escrituras: créalos solo donde hacen falta.
  • La clave primaria y UNIQUE ya tienen índice; las claves foráneas lo necesitan casi siempre.
  • En la próxima lección verás cómo agrupar varios cambios para que se hagan todos o ninguno: las transacciones.

Pon a prueba lo que has aprendido

[SQL] Existe el índice (fecha, importe) sobre pedidos. ¿Qué consulta NO puede aprovecharlo para filtrar?
CREATE INDEX idx_pedidos_fecha_importe ON pedidos (fecha, importe);

[SQL] ¿Qué inconveniente tiene crear un índice en cada columna de una tabla?

[SQL] Hay un índice sobre clientes(nombre). ¿En qué consulta es MENOS probable que se use?

[SQL] En SQLite, EXPLAIN QUERY PLAN muestra 'SCAN pedidos'. ¿Qué significa?

¿Te ha quedado claro? Márcala y verás tu progreso en el explorador.