Volver al índice
root@vps-donweb:~/docs/db$

MariaDB / MySQL

motor MariaDB 10.x
lenguaje SQL
categoría Bases de datos
CREAR Nueva base de datos

Para conectarse desde la consola LINUX usar el comando mysql -u root -p y te pide el passwd de mysql.

El primer paso es crear la base de datos. Todo lo demás vive adentro.

CREATE DATABASE mi_app
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

utf8mb4 soporta emojis y todos los caracteres Unicode. Siempre usarlo en proyectos web.

CREAR Solo si no existe (versión segura)
CREATE DATABASE IF NOT EXISTS mi_app
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

No da error si la base ya existe. Ideal para scripts de deploy.

VER Listar todas las bases
SHOW DATABASES;

Por defecto MariaDB incluye: information_schema, mysql, performance_schema. No tocarlas.

USAR Entrar a una base de datos

Antes de trabajar con tablas, hay que seleccionar la base activa.

USE mi_app;

Una vez ejecutado USE, todos los comandos siguientes afectan esa base hasta que cambies o cierres la sesión.

VER Base activa
SELECT DATABASE();
ELIMINAR Base entera
DROP DATABASE mi_app;
-- Versión segura:
DROP DATABASE IF EXISTS mi_app;

Irreversible. Borra la base y todo su contenido.

CREAR Tabla con columnas tipadas

Cada columna tiene un tipo de dato, restricciones y opcionalmente un valor por defecto.

CREATE TABLE usuarios (
  id         INT UNSIGNED    AUTO_INCREMENT PRIMARY KEY,
  nombre     VARCHAR(100)   NOT NULL,
  email      VARCHAR(255)   NOT NULL UNIQUE,
  activo     TINYINT(1)     DEFAULT 1,
  creado_en  TIMESTAMP      DEFAULT CURRENT_TIMESTAMP
);
CREAR Tabla con clave foránea (FOREIGN KEY)
CREATE TABLE posts (
  id          INT UNSIGNED   AUTO_INCREMENT PRIMARY KEY,
  usuario_id  INT UNSIGNED   NOT NULL,
  titulo      VARCHAR(200)   NOT NULL,
  contenido   TEXT,
  creado_en   TIMESTAMP      DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (usuario_id) REFERENCES usuarios(id)
    ON DELETE CASCADE
);

ON DELETE CASCADE: si se borra un usuario, sus posts se eliminan automáticamente.

VER Tablas y estructura

Listar tablas de la base activa:

SHOW TABLES;

Ver columnas de una tabla:

DESCRIBE usuarios;
-- equivalente:
SHOW COLUMNS FROM usuarios;

Ver el CREATE completo de una tabla (muy útil para revisar índices y FK):

SHOW CREATE TABLE usuarios;
MODIFICAR Alterar tabla existente
-- Agregar columna
ALTER TABLE usuarios ADD COLUMN apellido VARCHAR(100);

-- Agregar columna en posición específica
ALTER TABLE usuarios ADD COLUMN telefono VARCHAR(20) AFTER email;

-- Modificar tipo o restricción
ALTER TABLE usuarios MODIFY COLUMN nombre VARCHAR(150) NOT NULL;

-- Renombrar columna
ALTER TABLE usuarios
  CHANGE apellido apellido_paterno VARCHAR(100);

-- Eliminar columna
ALTER TABLE usuarios DROP COLUMN telefono;

-- Renombrar tabla
RENAME TABLE usuarios TO clientes;
VACIAR Solo los datos
TRUNCATE TABLE usuarios;

Borra todos los registros y resetea el AUTO_INCREMENT. No se puede deshacer.

ELIMINAR La tabla entera
DROP TABLE usuarios;
-- Versión segura:
DROP TABLE IF EXISTS usuarios;

Elimina la tabla y su estructura completa.

ÍNDICES Crear y gestionar
-- Ver índices de una tabla
SHOW INDEX FROM usuarios;

-- Crear índice en columna muy consultada
CREATE INDEX idx_email ON usuarios (email);

-- Índice único
CREATE UNIQUE INDEX idx_email_unique ON usuarios (email);

-- Eliminar índice
DROP INDEX idx_email ON usuarios;

Indexar columnas usadas frecuentemente en WHERE, JOIN u ORDER BY acelera mucho las consultas en tablas grandes.

INSERT Insertar registros
-- Un solo registro
INSERT INTO usuarios (nombre, email)
VALUES ('Juan García', 'juan@ejemplo.com');

-- Varios a la vez (más eficiente que múltiples INSERT)
INSERT INTO usuarios (nombre, email) VALUES
  ('Ana López',   'ana@ejemplo.com'),
  ('Pedro Ruiz',  'pedro@ejemplo.com'),
  ('María Silva', 'maria@ejemplo.com');

-- Insertar o actualizar si ya existe (UPSERT)
INSERT INTO usuarios (id, nombre, email)
VALUES (1, 'Juan García', 'juan@nuevo.com')
ON DUPLICATE KEY UPDATE
  email = VALUES(email);
SELECT Consultar registros
-- Todos los campos, todos los registros
SELECT * FROM usuarios;

-- Campos específicos con filtro
SELECT id, nombre, email
FROM usuarios
WHERE activo = 1
ORDER BY nombre ASC
LIMIT 10 OFFSET 0;

-- Contar registros
SELECT COUNT(*) FROM usuarios WHERE activo = 1;

-- Buscar con LIKE (% = cualquier cantidad de caracteres)
SELECT * FROM usuarios
WHERE nombre LIKE 'Juan%';

-- Obtener el último ID insertado
SELECT LAST_INSERT_ID();
UPDATE Modificar registros
-- Actualizar un registro específico
UPDATE usuarios
SET nombre = 'Juan Pérez', activo = 0
WHERE id = 1;

-- Actualizar múltiples con condición
UPDATE usuarios
SET activo = 0
WHERE creado_en < '2023-01-01';

-- Actualizar usando el valor actual
UPDATE productos
SET stock = stock - 1
WHERE id = 5;

Siempre usá WHERE en UPDATE. Sin él, se actualizan TODOS los registros de la tabla.

DELETE Eliminar registros
-- Eliminar un registro específico
DELETE FROM usuarios WHERE id = 5;

-- Eliminar varios con condición
DELETE FROM usuarios WHERE activo = 0;

-- Eliminar con LIMIT (por seguridad)
DELETE FROM logs
WHERE creado_en < '2023-01-01'
LIMIT 1000;

Sin WHERE borrás toda la tabla. Antes de ejecutar, probá con SELECT usando la misma condición.

TIP Verificar antes de modificar

Antes de un UPDATE o DELETE, ejecutá primero el mismo WHERE con SELECT para ver exactamente qué registros se verían afectados:

-- 1. Ver qué vas a afectar
SELECT * FROM usuarios WHERE activo = 0;

-- 2. Si el resultado es el esperado, ejecutar
DELETE FROM usuarios WHERE activo = 0;

También podés usar TRANSACTION para poder hacer rollback si algo sale mal.

TRANSACCIONES Control de errores
START TRANSACTION;

UPDATE cuentas SET saldo = saldo - 500 WHERE id = 1;
UPDATE cuentas SET saldo = saldo + 500 WHERE id = 2;

-- Si todo salió bien:
COMMIT;

-- Si algo falló, deshacer todo:
ROLLBACK;

Usar transacciones cuando varias operaciones deben ejecutarse juntas o ninguna.

INNER JOIN Unir tablas relacionadas

Devuelve solo los registros que tienen coincidencia en ambas tablas.

SELECT
  p.id,
  p.titulo,
  u.nombre AS autor,
  p.creado_en
FROM posts p
INNER JOIN usuarios u ON p.usuario_id = u.id
WHERE u.activo = 1
ORDER BY p.creado_en DESC;
LEFT JOIN Incluir registros sin coincidencia

Devuelve todos los registros de la tabla izquierda, aunque no tengan coincidencia en la derecha (NULL en esos campos).

-- Usuarios con o sin posts
SELECT
  u.nombre,
  COUNT(p.id) AS total_posts
FROM usuarios u
LEFT JOIN posts p ON u.id = p.usuario_id
GROUP BY u.id, u.nombre
ORDER BY total_posts DESC;

-- Usuarios que NO tienen posts
SELECT u.nombre
FROM usuarios u
LEFT JOIN posts p ON u.id = p.usuario_id
WHERE p.id IS NULL;
WHERE Condiciones y operadores
-- Múltiples condiciones
WHERE activo = 1 AND nombre LIKE 'Juan%'

-- Rango de valores
WHERE id BETWEEN 10 AND 50

-- Lista de valores
WHERE id IN (1, 3, 7, 12)

-- Nulos
WHERE apellido IS NULL
WHERE apellido IS NOT NULL

-- Fechas
WHERE creado_en >= '2024-01-01'
  AND creado_en <  '2025-01-01'

-- Búsqueda parcial (% = cualquier texto)
WHERE email LIKE '%@gmail.com'
GROUP BY Agrupar y agregar
-- Funciones de agregación
SELECT
  COUNT(*)          AS total,
  COUNT(apellido)   AS con_apellido,
  MAX(creado_en)    AS ultimo,
  MIN(creado_en)    AS primero,
  AVG(edad)         AS edad_promedio
FROM usuarios;

-- Agrupar con HAVING (filtra después de agrupar)
SELECT
  u.nombre,
  COUNT(p.id) AS cantidad_posts
FROM usuarios u
LEFT JOIN posts p ON u.id = p.usuario_id
GROUP BY u.id, u.nombre
HAVING cantidad_posts > 5
ORDER BY cantidad_posts DESC;

WHERE filtra antes de agrupar. HAVING filtra después. Usá HAVING solo con funciones de agregación.

ÚTILES Comandos de diagnóstico
-- Ver procesos activos
SHOW PROCESSLIST;

-- Ver variables del servidor
SHOW VARIABLES LIKE 'max_connections';
SHOW VARIABLES LIKE '%charset%';

-- Estado del servidor
SHOW STATUS LIKE 'Threads_connected';

-- Analizar rendimiento de una query
EXPLAIN SELECT * FROM posts WHERE usuario_id = 1;

-- Ver el motor de almacenamiento de las tablas
SHOW TABLE STATUS FROM mi_app;
CREAR Usuario nuevo

Buena práctica: cada aplicación web tiene su propio usuario con permisos mínimos. Nunca usar root desde la app.

-- Solo acceso local (recomendado para apps en el mismo VPS)
CREATE USER 'mi_app_user'@'localhost'
  IDENTIFIED BY 'contraseña_segura_aqui';

-- Acceso desde cualquier IP (solo si es necesario)
CREATE USER 'mi_app_user'@'%'
  IDENTIFIED BY 'contraseña_segura_aqui';

@'localhost' significa que solo puede conectarse desde el mismo servidor. Usar @'%' solo si la app está en otro servidor.

PERMISOS Asignar con GRANT
-- Todos los permisos sobre una base (para la app)
GRANT ALL PRIVILEGES ON mi_app.* TO 'mi_app_user'@'localhost';

-- Solo lectura (para reportes o backups)
GRANT SELECT ON mi_app.* TO 'lector'@'localhost';

-- Permisos específicos
GRANT SELECT, INSERT, UPDATE, DELETE ON mi_app.*
  TO 'mi_app_user'@'localhost';

-- Aplicar cambios inmediatamente
FLUSH PRIVILEGES;
VER Usuarios y permisos

Listar todos los usuarios:

SELECT User, Host
FROM mysql.global_priv;
      

Ver permisos de un usuario:

SHOW GRANTS FOR
  'mi_app_user'@'localhost';

Ver el usuario conectado actualmente:

SELECT USER(), CURRENT_USER();
CAMBIAR Contraseña
ALTER USER
  'mi_app_user'@'localhost'
  IDENTIFIED BY
  'nueva_contraseña';

FLUSH PRIVILEGES;
REVOCAR Permisos
REVOKE ALL PRIVILEGES ON
  mi_app.*
  FROM
  'mi_app_user'@'localhost';

FLUSH PRIVILEGES;
ELIMINAR Usuario
DROP USER 'mi_app_user'@'localhost';
Schema típico de una app web

Relaciones entre tablas. Las flechas representan claves foráneas.

📋 usuarios
PK id INT UNSIGNED
nombre VARCHAR(100)
email VARCHAR(255)
password_hash VARCHAR(255)
activo TINYINT(1)
creado_en TIMESTAMP
📝 posts
PK id INT UNSIGNED
FK usuario_id INT UNSIGNED
titulo VARCHAR(200)
contenido TEXT
slug VARCHAR(200)
publicado TINYINT(1)
💬 comentarios
PK id INT UNSIGNED
FK post_id INT UNSIGNED
FK usuario_id INT UNSIGNED
contenido TEXT
creado_en TIMESTAMP

usuarios → posts (1 a muchos)  ·  posts → comentarios (1 a muchos)

SCRIPT Crear el schema completo
-- Crear y seleccionar la base
CREATE DATABASE IF NOT EXISTS mi_web
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_unicode_ci;

USE mi_web;

-- Tabla de usuarios
CREATE TABLE usuarios (
  id            INT UNSIGNED   AUTO_INCREMENT PRIMARY KEY,
  nombre        VARCHAR(100)   NOT NULL,
  email         VARCHAR(255)   NOT NULL UNIQUE,
  password_hash VARCHAR(255)   NOT NULL,
  activo        TINYINT(1)     DEFAULT 1,
  creado_en     TIMESTAMP      DEFAULT CURRENT_TIMESTAMP
);

-- Tabla de posts
CREATE TABLE posts (
  id          INT UNSIGNED   AUTO_INCREMENT PRIMARY KEY,
  usuario_id  INT UNSIGNED   NOT NULL,
  titulo      VARCHAR(200)   NOT NULL,
  contenido   TEXT,
  slug        VARCHAR(200)   UNIQUE,
  publicado   TINYINT(1)     DEFAULT 0,
  creado_en   TIMESTAMP      DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (usuario_id)
    REFERENCES usuarios(id) ON DELETE CASCADE
);

-- Tabla de comentarios
CREATE TABLE comentarios (
  id          INT UNSIGNED   AUTO_INCREMENT PRIMARY KEY,
  post_id     INT UNSIGNED   NOT NULL,
  usuario_id  INT UNSIGNED   NOT NULL,
  contenido   TEXT           NOT NULL,
  creado_en   TIMESTAMP      DEFAULT CURRENT_TIMESTAMP,
  FOREIGN KEY (post_id)
    REFERENCES posts(id)    ON DELETE CASCADE,
  FOREIGN KEY (usuario_id)
    REFERENCES usuarios(id) ON DELETE CASCADE
);
QUERY Consulta compleja de ejemplo

Traer los últimos 10 posts publicados con nombre del autor y cantidad de comentarios:

SELECT
  p.id,
  p.titulo,
  p.slug,
  u.nombre            AS autor,
  p.creado_en,
  COUNT(c.id)         AS total_comentarios
FROM posts p
INNER JOIN usuarios u ON p.usuario_id = u.id
LEFT JOIN  comentarios c ON p.id = c.post_id
WHERE p.publicado = 1
  AND u.activo = 1
GROUP BY p.id, p.titulo, p.slug, u.nombre, p.creado_en
ORDER BY p.creado_en DESC
LIMIT 10;
BACKUP Exportar e importar la base
-- Exportar (desde la terminal del VPS, no desde MySQL)
mysqldump -u root -p mi_web > mi_web_backup.sql

-- Exportar comprimido
mysqldump -u root -p mi_web | gzip > mi_web_$(date +%Y%m%d).sql.gz

-- Importar
mysql -u root -p mi_web < mi_web_backup.sql

-- Importar comprimido
gunzip < mi_web_20240101.sql.gz | mysql -u root -p mi_web

Estos comandos se ejecutan en la terminal del VPS, no dentro de la consola MySQL.