USER() y CURRENT_USER()

Saber quién está ejecutando una consulta es fundamental para la seguridad, la auditoría y el control de acceso en cualquier base de datos. MySQL ofrece varias funciones que identifican al usuario de la sesión actual, pero no todas devuelven lo mismo. Comprender la diferencia entre USER() y CURRENT_USER() te evitará confusiones y te permitirá construir sistemas de auditoría precisos, triggers que registren correctamente quién modificó los datos y procedimientos almacenados que adapten su comportamiento según la identidad del usuario.

La distinción clave es sutil pero importante: USER() devuelve cómo te presentaste al servidor (qué credenciales usaste para conectar), mientras que CURRENT_USER() devuelve cómo el servidor te reconoció (qué cuenta de usuario coincidió con tu conexión según las reglas de autenticación).

Sintaxis de USER() y CURRENT_USER()

Ambas funciones se invocan sin argumentos y devuelven una cadena con el formato 'usuario@host'.

SELECT USER();
SELECT CURRENT_USER();

En la mayoría de las conexiones, ambas devuelven el mismo valor. La diferencia aparece cuando el host desde el que te conectas coincide con una regla de autenticación que usa comodines.

-- Conexión desde localhost con usuario 'admin'
SELECT USER() AS usuario_conexion, CURRENT_USER() AS usuario_autenticado;
-- Resultado típico:
-- usuario_conexion: admin@localhost
-- usuario_autenticado: admin@localhost

La diferencia entre USER() y CURRENT_USER()

Para entender cuándo estas funciones difieren, necesitas comprender cómo MySQL resuelve la autenticación. Cuando te conectas, proporcionas un nombre de usuario y MySQL detecta tu host de origen. Luego busca en la tabla mysql.user una fila que coincida con esa combinación. Si tu host exacto no tiene una entrada pero existe una con comodín %, MySQL te autentica contra esa regla.

Imagina que existe un usuario creado así:

CREATE USER 'analista'@'%' IDENTIFIED BY 'clave_segura';
GRANT SELECT ON tienda_online.* TO 'analista'@'%';

El comodín % significa que este usuario puede conectarse desde cualquier host. Si analista se conecta desde la máquina 192.168.1.50, las funciones devolverán valores diferentes.

-- Conectado como analista desde 192.168.1.50
SELECT USER();
-- Resultado: analista@192.168.1.50
 
SELECT CURRENT_USER();
-- Resultado: analista@%

USER() refleja el host real de la conexión (192.168.1.50), mientras que CURRENT_USER() muestra la cuenta de usuario que el servidor utilizó para autenticar y asignar privilegios (analista@%). Esta distinción es relevante porque los privilegios se asocian a la cuenta de CURRENT_USER(), no al host literal de USER().

SESSION_USER() y SYSTEM_USER() como sinónimos

MySQL ofrece dos funciones adicionales que son sinónimos exactos de USER(): SESSION_USER() y SYSTEM_USER(). Las tres devuelven el mismo resultado sin ninguna diferencia.

-- Todas devuelven lo mismo
SELECT
  USER() AS user_fn,
  SESSION_USER() AS session_user_fn,
  SYSTEM_USER() AS system_user_fn;

La existencia de estos sinónimos se debe a razones históricas y de compatibilidad con otros sistemas de bases de datos. En la práctica, la convención más extendida es usar USER() para identificar la conexión y CURRENT_USER() para identificar la cuenta autenticada. Los sinónimos rara vez se usan en código nuevo.

Uso en auditoría: registrar quién hace cambios

Uno de los usos más importantes de estas funciones es construir sistemas de auditoría que registren quién realiza cada operación sobre los datos. La tabla de auditoría almacena automáticamente la identidad del usuario junto con los detalles de cada cambio.

-- Tabla principal de clientes
CREATE TABLE clientes (
  id INT AUTO_INCREMENT PRIMARY KEY,
  nombre VARCHAR(100) NOT NULL,
  email VARCHAR(150) NOT NULL,
  ciudad VARCHAR(100),
  fecha_registro TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
-- Tabla de auditoría
CREATE TABLE auditoria_clientes (
  id INT AUTO_INCREMENT PRIMARY KEY,
  cliente_id INT NOT NULL,
  campo_modificado VARCHAR(50) NOT NULL,
  valor_anterior VARCHAR(255),
  valor_nuevo VARCHAR(255),
  usuario_conexion VARCHAR(100) NOT NULL,
  usuario_autenticado VARCHAR(100) NOT NULL,
  fecha_cambio TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

La tabla de auditoría almacena tanto USER() como CURRENT_USER() porque la combinación de ambos proporciona información completa: sabes desde qué máquina se conectó el usuario y con qué cuenta fue autenticado. Esto es invaluable para investigaciones de seguridad donde necesitas rastrear el origen exacto de un cambio.

Uso en triggers para log de modificaciones

Los triggers BEFORE UPDATE son el mecanismo ideal para alimentar tablas de auditoría de forma automática. Cada vez que se modifica un registro, el trigger captura los valores anteriores y nuevos junto con la identidad del usuario.

DELIMITER //
 
CREATE TRIGGER trg_auditoria_clientes_update
BEFORE UPDATE ON clientes
FOR EACH ROW
BEGIN
  -- Auditar cambio de nombre
  IF OLD.nombre != NEW.nombre THEN
    INSERT INTO auditoria_clientes
      (cliente_id, campo_modificado, valor_anterior, valor_nuevo,
       usuario_conexion, usuario_autenticado)
    VALUES
      (OLD.id, 'nombre', OLD.nombre, NEW.nombre,
       USER(), CURRENT_USER());
  END IF;
 
  -- Auditar cambio de email
  IF OLD.email != NEW.email THEN
    INSERT INTO auditoria_clientes
      (cliente_id, campo_modificado, valor_anterior, valor_nuevo,
       usuario_conexion, usuario_autenticado)
    VALUES
      (OLD.id, 'email', OLD.email, NEW.email,
       USER(), CURRENT_USER());
  END IF;
 
  -- Auditar cambio de ciudad
  IF OLD.ciudad != NEW.ciudad OR (OLD.ciudad IS NULL AND NEW.ciudad IS NOT NULL)
     OR (OLD.ciudad IS NOT NULL AND NEW.ciudad IS NULL) THEN
    INSERT INTO auditoria_clientes
      (cliente_id, campo_modificado, valor_anterior, valor_nuevo,
       usuario_conexion, usuario_autenticado)
    VALUES
      (OLD.id, 'ciudad', OLD.ciudad, NEW.ciudad,
       USER(), CURRENT_USER());
  END IF;
END //
 
DELIMITER ;

Ahora, cada vez que alguien modifica un cliente, el trigger registra automáticamente qué cambió, quién lo hizo y cuándo.

-- Insertar datos de ejemplo
INSERT INTO clientes (nombre, email, ciudad)
VALUES ('María García', 'maria@ejemplo.com', 'Barcelona');
 
-- Modificar un cliente (esto activa el trigger)
UPDATE clientes
SET ciudad = 'Madrid', email = 'maria.garcia@ejemplo.com'
WHERE id = 1;
 
-- Consultar la auditoría
SELECT
  cliente_id,
  campo_modificado,
  valor_anterior,
  valor_nuevo,
  usuario_conexion,
  fecha_cambio
FROM auditoria_clientes
ORDER BY fecha_cambio;

El resultado mostrará dos registros de auditoría: uno para el cambio de ciudad y otro para el cambio de email, ambos con la información del usuario que realizó la operación.

Ejemplo completo de sistema de auditoría

En un sistema real, querrás auditar también las inserciones y eliminaciones, no solo las actualizaciones. A continuación se muestra un sistema completo con triggers para las tres operaciones.

-- Tabla de auditoría ampliada
CREATE TABLE log_operaciones (
  id INT AUTO_INCREMENT PRIMARY KEY,
  tabla_afectada VARCHAR(64) NOT NULL,
  tipo_operacion ENUM('INSERT', 'UPDATE', 'DELETE') NOT NULL,
  registro_id INT NOT NULL,
  detalle TEXT,
  usuario VARCHAR(100) NOT NULL,
  host_origen VARCHAR(100) NOT NULL,
  fecha TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
 
DELIMITER //
 
-- Trigger para INSERT
CREATE TRIGGER trg_log_clientes_insert
AFTER INSERT ON clientes
FOR EACH ROW
BEGIN
  INSERT INTO log_operaciones
    (tabla_afectada, tipo_operacion, registro_id, detalle, usuario, host_origen)
  VALUES
    ('clientes', 'INSERT', NEW.id,
     CONCAT('Nuevo cliente: ', NEW.nombre, ' (', NEW.email, ')'),
     CURRENT_USER(),
     SUBSTRING_INDEX(USER(), '@', -1));
END //
 
-- Trigger para DELETE
CREATE TRIGGER trg_log_clientes_delete
BEFORE DELETE ON clientes
FOR EACH ROW
BEGIN
  INSERT INTO log_operaciones
    (tabla_afectada, tipo_operacion, registro_id, detalle, usuario, host_origen)
  VALUES
    ('clientes', 'DELETE', OLD.id,
     CONCAT('Cliente eliminado: ', OLD.nombre, ' (', OLD.email, ')'),
     CURRENT_USER(),
     SUBSTRING_INDEX(USER(), '@', -1));
END //
 
DELIMITER ;

Observa cómo SUBSTRING_INDEX(USER(), '@', -1) extrae solo la parte del host de USER(), mientras que CURRENT_USER() se almacena completo. Este patrón te permite consultar la auditoría filtrando por host de origen, lo cual es útil para detectar accesos desde ubicaciones inesperadas.

Consultar el usuario en procedimientos almacenados

Dentro de procedimientos almacenados, hay un matiz importante: CURRENT_USER() puede devolver el usuario que creó el procedimiento en lugar del usuario que lo ejecuta, dependiendo de la cláusula SQL SECURITY del procedimiento.

DELIMITER //
 
-- Procedimiento con SQL SECURITY DEFINER (por defecto)
CREATE PROCEDURE ver_usuario_definer()
SQL SECURITY DEFINER
BEGIN
  SELECT
    USER() AS usuario_conexion,
    CURRENT_USER() AS usuario_autenticado;
END //
 
-- Procedimiento con SQL SECURITY INVOKER
CREATE PROCEDURE ver_usuario_invoker()
SQL SECURITY INVOKER
BEGIN
  SELECT
    USER() AS usuario_conexion,
    CURRENT_USER() AS usuario_autenticado;
END //
 
DELIMITER ;

Con SQL SECURITY DEFINER, CURRENT_USER() dentro del procedimiento devuelve el usuario que lo creó. Con SQL SECURITY INVOKER, devuelve el usuario que lo ejecuta. USER() siempre devuelve el usuario de la conexión activa, independientemente de la cláusula de seguridad. Esta distinción es crítica al diseñar sistemas de auditoría que usan procedimientos almacenados.

Errores comunes

El error más frecuente es asumir que USER() y CURRENT_USER() siempre devuelven lo mismo. En entornos con usuarios definidos con comodines en el host (%, 192.168.%), los valores difieren y usar una función en lugar de la otra puede dar resultados incorrectos en la auditoría.

Otro error habitual es no considerar el efecto de SQL SECURITY DEFINER en procedimientos almacenados. Si un procedimiento creado por root@localhost registra CURRENT_USER() en una tabla de auditoría, todos los registros mostrarán root@localhost como autor del cambio, ocultando la identidad del usuario real que invocó el procedimiento. Para auditoría dentro de procedimientos, usa USER() o define el procedimiento con SQL SECURITY INVOKER.

Un tercer error es intentar usar estas funciones para control de acceso a nivel de fila (row-level security). Aunque puedes agregar una columna propietario a una tabla y filtrar con WHERE propietario = CURRENT_USER(), esto no es un mecanismo de seguridad robusto. Los usuarios con privilegios suficientes pueden saltarse estos filtros. Para seguridad real, usa el sistema de privilegios de MySQL con GRANT.

Cuándo usar USER() y CURRENT_USER()

Utiliza USER() cuando necesites el host real de la conexión, como en registros de seguridad donde el origen de la conexión importa. Utiliza CURRENT_USER() cuando necesites la identidad que MySQL usa para determinar privilegios, como en verificaciones de permisos o en procedimientos que necesiten saber bajo qué cuenta se ejecutan. En sistemas de auditoría completos, registra ambas funciones para tener la información más rica posible sobre cada operación.

Escrito por Eduardo Lázaro