Índice único

Un índice único (UNIQUE INDEX) combina dos funciones: acelera las búsquedas como cualquier índice y garantiza que no existan valores duplicados en la columna indexada. Es la implementación interna de la restricción UNIQUE que vimos en la sección de restricciones.

Sintaxis

-- Con CREATE INDEX
CREATE UNIQUE INDEX nombre_indice ON tabla (columna);
 
-- Con ALTER TABLE
ALTER TABLE tabla ADD UNIQUE INDEX nombre_indice (columna);
 
-- En CREATE TABLE
CREATE TABLE tabla (
    columna tipo,
    UNIQUE INDEX nombre_indice (columna)
);
 
-- Atajo: la restricción UNIQUE crea el índice automáticamente
CREATE TABLE tabla (
    columna tipo UNIQUE
);

Ejemplo básico

CREATE TABLE codigos_descuento (
    id INT AUTO_INCREMENT PRIMARY KEY,
    codigo VARCHAR(20) NOT NULL,
    descuento DECIMAL(5,2) NOT NULL,
    usos_restantes INT DEFAULT 1,
    UNIQUE INDEX uq_codigo (codigo)
);
 
INSERT INTO codigos_descuento (codigo, descuento, usos_restantes) VALUES
('BIENVENIDO10', 10.00, 100),
('VERANO25', 25.00, 50),
('VIP50', 50.00, 10);
 
-- Buscar un código es rápido gracias al índice
EXPLAIN SELECT * FROM codigos_descuento WHERE codigo = 'VIP50';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEcodigos_descuentoconstuq_codigouq_codigo1NULL

type: const es el tipo de acceso más rápido. MySQL sabe que solo puede haber una fila con ese valor.

-- Intentar insertar un código duplicado falla
-- INSERT INTO codigos_descuento (codigo, descuento) VALUES ('VIP50', 15.00);
-- Error: Duplicate entry 'VIP50' for key 'uq_codigo'

Índices únicos en tienda_mysql

SELECT TABLE_NAME, INDEX_NAME, COLUMN_NAME
FROM information_schema.STATISTICS
WHERE TABLE_SCHEMA = 'tienda_mysql'
  AND NON_UNIQUE = 0
  AND INDEX_NAME != 'PRIMARY'
ORDER BY TABLE_NAME;
TABLE_NAMEINDEX_NAMECOLUMN_NAME
clientesemailemail
empleadosemailemail

Las tablas clientes y empleados tienen índices únicos en la columna email, creados automáticamente por la restricción UNIQUE en el CREATE TABLE.

-- El índice único se usa para búsquedas rápidas
EXPLAIN SELECT * FROM clientes WHERE email = 'maria.garcia@email.com';
idselect_typetabletypepossible_keyskeyrowsExtra
1SIMPLEclientesconstemailemail1NULL

Índice único compuesto

Un índice único puede cubrir múltiples columnas. La combinación de valores debe ser única, pero cada columna individual puede tener duplicados:

CREATE TABLE inscripciones (
    id INT AUTO_INCREMENT PRIMARY KEY,
    estudiante_id INT NOT NULL,
    curso_id INT NOT NULL,
    fecha DATE NOT NULL,
    UNIQUE INDEX uq_estudiante_curso (estudiante_id, curso_id)
);
 
-- Un estudiante puede inscribirse en diferentes cursos
INSERT INTO inscripciones (estudiante_id, curso_id, fecha) VALUES
(1, 101, '2026-02-01'),
(1, 102, '2026-02-01'),
(2, 101, '2026-02-01');
 
-- Pero no puede inscribirse dos veces al mismo curso
-- INSERT INTO inscripciones (estudiante_id, curso_id, fecha) VALUES (1, 101, '2026-02-15');
-- Error: Duplicate entry '1-101' for key 'uq_estudiante_curso'

NULL en índices únicos

A diferencia de la mayoría de bases de datos, MySQL permite múltiples valores NULL en una columna con índice único. NULL no se considera igual a otro NULL:

CREATE TABLE perfiles (
    id INT AUTO_INCREMENT PRIMARY KEY,
    usuario VARCHAR(50) NOT NULL,
    twitter VARCHAR(50),
    UNIQUE INDEX uq_twitter (twitter)
);
 
-- Múltiples NULLs son válidos
INSERT INTO perfiles (usuario, twitter) VALUES
('María', '@maria_dev'),
('Carlos', NULL),
('Ana', NULL);
 
SELECT * FROM perfiles;
idusuariotwitter
1María@maria_dev
2Carlos
3Ana

Carlos y Ana tienen NULL en twitter y ambos se aceptan. Pero no puedes insertar otro @maria_dev.

UNIQUE vs PRIMARY KEY

CaracterísticaPRIMARY KEYUNIQUE INDEX
NULLNo permitePermite (múltiples)
Por tablaSolo unaVarias
Índice clusteredSí (InnoDB)No
Creado automáticamenteCon PRIMARY KEYCon UNIQUE

Crear índice único sobre datos existentes

Si la tabla ya tiene datos duplicados, la creación del índice falla:

-- Verificar duplicados antes de crear el índice
SELECT ciudad, COUNT(*) AS repeticiones
FROM clientes
GROUP BY ciudad
HAVING COUNT(*) > 1;
ciudadrepeticiones
Madrid2
Barcelona2
Zaragoza2
Valencia2

Como hay ciudades duplicadas, no podemos crear un índice único sobre ciudad. Esto es correcto: múltiples clientes pueden vivir en la misma ciudad.

Limpieza

DROP TABLE IF EXISTS codigos_descuento;
DROP TABLE IF EXISTS inscripciones;
DROP TABLE IF EXISTS perfiles;

En el siguiente artículo veremos los índices invisibles, una funcionalidad de MySQL 8.0 que permite desactivar índices sin eliminarlos.

Escrito por Eduardo Lázaro