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.
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.
SHOW DATABASES;
Por defecto MariaDB incluye: information_schema, mysql, performance_schema. No tocarlas.
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.
SELECT DATABASE();
DROP DATABASE mi_app; -- Versión segura: DROP DATABASE IF EXISTS mi_app;
Irreversible. Borra la base y todo su contenido.
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 );
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.
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;
-- 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;
TRUNCATE TABLE usuarios;
Borra todos los registros y resetea el AUTO_INCREMENT. No se puede deshacer.
DROP TABLE usuarios; -- Versión segura: DROP TABLE IF EXISTS usuarios;
Elimina la tabla y su estructura completa.
-- 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.
-- 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);
-- 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();
-- 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.
-- 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.
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.
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.
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;
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;
-- 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'
-- 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.
-- 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;
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.
-- 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;
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();
ALTER USER 'mi_app_user'@'localhost' IDENTIFIED BY 'nueva_contraseña'; FLUSH PRIVILEGES;
REVOKE ALL PRIVILEGES ON mi_app.* FROM 'mi_app_user'@'localhost'; FLUSH PRIVILEGES;
DROP USER 'mi_app_user'@'localhost';
Relaciones entre tablas. Las flechas representan claves foráneas.
usuarios → posts (1 a muchos) · posts → comentarios (1 a muchos)
-- 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 );
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;
-- 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.