LAST_INSERT_ID()

Uno de los patrones más habituales en bases de datos relacionales es insertar un registro en una tabla padre y luego usar su clave primaria generada automáticamente para insertar registros relacionados en tablas hijas. La función LAST_INSERT_ID() es la herramienta que MySQL proporciona para este flujo: devuelve el último valor AUTO_INCREMENT generado por una sentencia INSERT en la conexión actual. Es segura en entornos concurrentes porque cada conexión mantiene su propio valor, independiente de lo que hagan otras conexiones simultáneas.

Comprender cómo funciona LAST_INSERT_ID() y sus particularidades te ahorrará bugs sutiles y difíciles de diagnosticar, especialmente cuando trabajas con inserciones múltiples o transacciones complejas.

Sintaxis de LAST_INSERT_ID()

La función se invoca sin argumentos para obtener el último valor AUTO_INCREMENT generado, o con un argumento para establecer un valor manualmente.

-- Obtener el último ID generado
SELECT LAST_INSERT_ID();
 
-- Establecer un valor manualmente
SELECT LAST_INSERT_ID(expresion);

Cuando se invoca sin argumentos, devuelve un entero sin signo (BIGINT UNSIGNED) que representa el último valor AUTO_INCREMENT generado por una sentencia INSERT exitosa en la conexión actual. Si no se ha ejecutado ningún INSERT con AUTO_INCREMENT en la sesión, devuelve 0.

-- Al iniciar una sesión nueva
SELECT LAST_INSERT_ID();
-- Resultado: 0

Flujo típico: insertar padre e hijos

El caso de uso más común de LAST_INSERT_ID() es el patrón de inserción jerárquica donde insertas un registro principal y luego necesitas su ID para crear registros dependientes vinculados mediante una clave foránea.

-- Tablas relacionadas
CREATE TABLE pedidos (
  id INT AUTO_INCREMENT PRIMARY KEY,
  cliente_id INT NOT NULL,
  fecha_pedido DATE NOT NULL DEFAULT (CURRENT_DATE),
  estado ENUM('pendiente', 'procesando', 'enviado', 'entregado') DEFAULT 'pendiente',
  total DECIMAL(10,2) NOT NULL DEFAULT 0.00
);
 
CREATE TABLE detalle_pedido (
  id INT AUTO_INCREMENT PRIMARY KEY,
  pedido_id INT NOT NULL,
  producto VARCHAR(200) NOT NULL,
  cantidad INT NOT NULL,
  precio_unitario DECIMAL(10,2) NOT NULL,
  FOREIGN KEY (pedido_id) REFERENCES pedidos(id)
);

Con estas tablas definidas, el flujo de inserción usa LAST_INSERT_ID() como puente entre la tabla padre y la tabla hija.

-- Paso 1: Insertar el pedido
INSERT INTO pedidos (cliente_id, total)
VALUES (42, 157.50);
 
-- Paso 2: Obtener el ID generado
-- LAST_INSERT_ID() devuelve el id del pedido que acabamos de crear
 
-- Paso 3: Insertar los detalles usando ese ID
INSERT INTO detalle_pedido (pedido_id, producto, cantidad, precio_unitario)
VALUES
  (LAST_INSERT_ID(), 'Teclado mecánico RGB', 1, 89.99),
  (LAST_INSERT_ID(), 'Alfombrilla XL', 1, 24.50),
  (LAST_INSERT_ID(), 'Cable USB-C trenzado', 2, 21.50);

Observa que LAST_INSERT_ID() mantiene el mismo valor durante las tres inserciones de detalle porque no se ha ejecutado otro INSERT en una tabla con AUTO_INCREMENT entre medias. El valor solo cambia cuando un nuevo INSERT genera un valor AUTO_INCREMENT.

Comportamiento per-connection

Una de las propiedades más importantes de LAST_INSERT_ID() es que el valor es específico de cada conexión. Esto significa que si dos usuarios insertan registros simultáneamente, cada uno obtiene su propio ID sin interferencias.

-- Conexión A
INSERT INTO pedidos (cliente_id, total) VALUES (10, 250.00);
SELECT LAST_INSERT_ID();  -- Devuelve 101
 
-- Conexión B (simultáneamente)
INSERT INTO pedidos (cliente_id, total) VALUES (20, 180.00);
SELECT LAST_INSERT_ID();  -- Devuelve 102
 
-- Conexión A (sigue viendo su propio valor)
SELECT LAST_INSERT_ID();  -- Sigue devolviendo 101

Esta característica hace que LAST_INSERT_ID() sea segura en aplicaciones web con múltiples conexiones concurrentes. No necesitas usar bloqueos ni transacciones especiales para proteger el valor. Cada conexión mantiene su estado de forma aislada.

Comportamiento con INSERT de múltiples filas

Cuando ejecutas un INSERT con múltiples filas, LAST_INSERT_ID() devuelve el valor AUTO_INCREMENT generado para la primera fila insertada, no para la última. Este comportamiento sorprende a muchos desarrolladores y es una fuente habitual de bugs.

-- Tabla de ejemplo
CREATE TABLE productos (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(200) NOT NULL,
  precio DECIMAL(10,2) NOT NULL
);
 
-- Insertar tres productos de una vez
INSERT INTO productos (nombre, precio)
VALUES
  ('Monitor 27 pulgadas', 349.99),
  ('Webcam HD', 59.99),
  ('Soporte monitor', 45.00);
 
SELECT LAST_INSERT_ID();
-- Si el AUTO_INCREMENT estaba en 1, devuelve 1 (no 3)

El razonamiento detrás de este diseño es que puedes calcular los IDs de todas las filas insertadas a partir del primer ID. Si LAST_INSERT_ID() devuelve 1 y insertaste 3 filas, los IDs asignados son 1, 2 y 3. Esto funciona siempre que el incremento de AUTO_INCREMENT sea 1 (el valor por defecto).

-- Calcular todos los IDs generados tras un INSERT múltiple
SET @primer_id = LAST_INSERT_ID();
SET @filas_insertadas = ROW_COUNT();
 
SELECT
  @primer_id AS primer_id,
  @primer_id + @filas_insertadas - 1 AS ultimo_id;

LAST_INSERT_ID(expr): establecer un valor manualmente

La función acepta un argumento opcional que establece el valor que devolverá la próxima llamada a LAST_INSERT_ID() sin argumentos. Esto es útil para implementar secuencias personalizadas o contadores que no usan AUTO_INCREMENT.

-- Tabla de secuencias personalizadas
CREATE TABLE secuencias (
  nombre VARCHAR(50) PRIMARY KEY,
  valor_actual BIGINT NOT NULL DEFAULT 0
);
 
INSERT INTO secuencias (nombre, valor_actual) VALUES ('factura', 1000);
 
-- Obtener el siguiente número de factura de forma atómica
UPDATE secuencias
SET valor_actual = LAST_INSERT_ID(valor_actual + 1)
WHERE nombre = 'factura';
 
SELECT LAST_INSERT_ID();
-- Resultado: 1001

En este patrón, LAST_INSERT_ID(valor_actual + 1) hace dos cosas simultáneamente: actualiza el valor en la tabla y establece el resultado de LAST_INSERT_ID() para la conexión actual. Esto evita condiciones de carrera porque el UPDATE y la asignación ocurren de forma atómica.

-- Uso práctico: generar número de factura
UPDATE secuencias
SET valor_actual = LAST_INSERT_ID(valor_actual + 1)
WHERE nombre = 'factura';
 
INSERT INTO facturas (numero, cliente_id, importe, fecha)
VALUES (
  CONCAT('FAC-', LPAD(LAST_INSERT_ID(), 6, '0')),
  42,
  1250.00,
  CURRENT_DATE
);
-- numero será algo como 'FAC-001002'

Uso dentro de transacciones

En contextos transaccionales, LAST_INSERT_ID() se comporta de forma predecible: el valor se establece cuando se ejecuta el INSERT, independientemente de si la transacción se confirma o se revierte. Sin embargo, si la transacción se revierte, el valor de AUTO_INCREMENT no se reutiliza, lo que puede dejar huecos en la secuencia.

START TRANSACTION;
 
INSERT INTO pedidos (cliente_id, total)
VALUES (15, 320.00);
 
SET @pedido_id = LAST_INSERT_ID();
 
INSERT INTO detalle_pedido (pedido_id, producto, cantidad, precio_unitario)
VALUES
  (@pedido_id, 'Disco SSD 1TB', 1, 89.99),
  (@pedido_id, 'Memoria RAM 16GB', 2, 115.00);
 
-- Actualizar el total del pedido
UPDATE pedidos
SET total = (
  SELECT SUM(cantidad * precio_unitario)
  FROM detalle_pedido
  WHERE pedido_id = @pedido_id
)
WHERE id = @pedido_id;
 
COMMIT;

Almacenar LAST_INSERT_ID() en una variable de usuario (@pedido_id) al inicio de la transacción es una buena práctica. De este modo, aunque ejecutes otros INSERTs con AUTO_INCREMENT durante la transacción, no pierdes la referencia al ID del pedido.

La variable @@identity

La variable @@identity no existe en MySQL como tal. A diferencia de SQL Server, donde @@IDENTITY devuelve el último valor de identidad generado en cualquier ámbito, MySQL solo ofrece LAST_INSERT_ID(). Si vienes de SQL Server, ten en cuenta esta diferencia: en MySQL, LAST_INSERT_ID() cumple el rol de @@IDENTITY pero con un alcance limitado a la conexión actual y sin verse afectado por triggers (un trigger que inserte en otra tabla con AUTO_INCREMENT no modifica el valor de LAST_INSERT_ID() de la sesión exterior).

-- En MySQL, esto es lo que necesitas
SELECT LAST_INSERT_ID();
 
-- NO existe @@identity en MySQL
-- SELECT @@identity;  -- Esto daría error

Errores comunes

El error más grave y frecuente es asumir que LAST_INSERT_ID() devuelve el último ID generado globalmente en la tabla. Si tu aplicación tiene múltiples conexiones insertando registros simultáneamente y usas SELECT MAX(id) FROM pedidos en lugar de LAST_INSERT_ID(), obtendrás el ID de otro usuario. Esta confusión es la causa número uno de datos vinculados incorrectamente en tablas hijas.

-- INCORRECTO: vulnerable a condiciones de carrera
INSERT INTO pedidos (cliente_id, total) VALUES (42, 100.00);
SET @id = (SELECT MAX(id) FROM pedidos);  -- ¡Puede ser el pedido de otro usuario!
 
-- CORRECTO: seguro en concurrencia
INSERT INTO pedidos (cliente_id, total) VALUES (42, 100.00);
SET @id = LAST_INSERT_ID();  -- Siempre tu pedido

Otro error habitual es no entender el comportamiento con INSERT múltiple. Si insertas 5 filas y esperas que LAST_INSERT_ID() devuelva el ID de la quinta fila, tus registros hijos tendrán un pedido_id incorrecto.

Un tercer error es llamar a LAST_INSERT_ID() demasiado tarde. Si entre tu INSERT y la llamada a LAST_INSERT_ID() ejecutas otro INSERT en una tabla con AUTO_INCREMENT, el valor se habrá sobrescrito. Captura siempre el valor inmediatamente después del INSERT que lo genera.

-- PELIGROSO: otro INSERT puede sobrescribir el valor
INSERT INTO pedidos (cliente_id, total) VALUES (42, 100.00);
INSERT INTO notificaciones (mensaje) VALUES ('Nuevo pedido creado');  -- Tiene AUTO_INCREMENT
SELECT LAST_INSERT_ID();  -- ¡Devuelve el ID de notificaciones, no de pedidos!
 
-- SEGURO: capturar inmediatamente
INSERT INTO pedidos (cliente_id, total) VALUES (42, 100.00);
SET @pedido_id = LAST_INSERT_ID();
INSERT INTO notificaciones (mensaje) VALUES ('Nuevo pedido creado');
-- @pedido_id sigue teniendo el ID correcto del pedido

Cuándo usar LAST_INSERT_ID()

Usa LAST_INSERT_ID() siempre que necesites el valor AUTO_INCREMENT generado por un INSERT para utilizarlo en operaciones posteriores de la misma sesión. El escenario clásico es la inserción de registros padre-hijo, pero también es útil para implementar secuencias personalizadas con el patrón UPDATE ... SET col = LAST_INSERT_ID(col + 1), para registrar en logs el ID del registro recién creado, y para devolver el ID generado a la aplicación cliente sin necesidad de una consulta SELECT adicional. Recuerda siempre capturar el valor en una variable inmediatamente después del INSERT para evitar que un INSERT posterior lo sobrescriba.

Escrito por Eduardo Lázaro