Relational Database

MySQL

MySQL es la base de datos relacional detrás de una gran parte de la web. InnoDB le otorga transacciones reales, la replicación facilita el escalado de lecturas y su ecosistema es enorme.

intermediate15 min readUpdated 16 sept 2026
schema.sql
sql
-- schema.sql
CREATE TABLE products (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  sku         VARCHAR(64) NOT NULL,
  name        VARCHAR(200) NOT NULL,
  price_cents INT UNSIGNED NOT NULL,
  stock       INT NOT NULL DEFAULT 0,
  attributes  JSON NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
Lanzamiento
1995
Licencia
GPL / comercial (Oracle)
Motor de almacenamiento
InnoDB (predeterminado)
Aislamiento predeterminado
Repeatable Read
Charset
utf8mb4
Última versión mayor
9.x

Por que importa

Por qué MySQL sigue impulsando la web

Transacciones InnoDB

El bloqueo a nivel de fila, las transacciones ACID y la recuperación tras fallos son gestionados por el motor predeterminado, por lo que la consistencia no es algo secundario.

Replicación para escalado de lecturas

Un primario transmite su binary log a las réplicas, y estas absorben el tráfico de lectura o quedan en espera para un failover.

Un ecosistema enorme

Cada framework, lenguaje y proveedor de hosting soporta MySQL, lo que significa que los tutoriales, drivers y servicios gestionados están en todas partes.

La imagen completa

Las tres ideas detrás de MySQL

InnoDB para transacciones, índices para velocidad y replicación para el escalado de lecturas: el motor ha sido refinado en torno a estas tres funciones.

InnoDB

Commit

El motor de almacenamiento predeterminado proporciona bloqueo a nivel de fila, lecturas MVCC, claves foráneas y recuperación automática tras fallos.

Índices

Locate

Los índices B-tree convierten los escaneos completos en búsquedas directas, y la elección del optimizador es visible a través de EXPLAIN.

Conexiones

Serve

MySQL gestiona cada conexión con un hilo, por lo que los pools en el lado de la aplicación evitan que miles de clientes saturen el servidor.

Modelo de datos

Una fila por producto

Una fila representa un único producto vendible, con un SKU estable, un precio entero y un conjunto JSON de atributos variables.

La tabla de productosMySQL table
  • idbigint unsignedClave primaria AUTO_INCREMENT
  • skuvarchar(64)Unidad de mantenimiento de stock (SKU) única y legible
  • namevarchar(200)Nombre de visualización mostrado a los clientes
  • price_centsint unsignedPrecio en unidades menores; evita el uso de punto flotante para dinero
  • stockintUnidades disponibles; puede ser negativo durante correcciones
  • attributesjsonPropiedades variables como color, tamaño o material

Una fila representa un único producto vendible, con un SKU estable, un precio entero y un conjunto JSON de atributos variables.

Una breve historia

De 18 etiquetas a un estandar vivo

  1. 1995

    Lanzamiento de MySQL

    Michael Widenius y David Axmark publican una base de datos rápida y sencilla orientada a cargas de trabajo web con alta lectura.

    95
  2. 2000

    Licencia GPL y el stack LAMP

    La licencia de código abierto ayuda a MySQL a convertirse en la M de Linux, Apache, MySQL y PHP.

    00
  3. 2008

    Sun adquiere MySQL AB

    La empresa es comprada por Sun Microsystems, y luego heredada por Oracle dos años después.

    08
  4. 2010

    InnoDB se convierte en el predeterminado

    MySQL 5.5 establece el motor transaccional como predeterminado, retirando MyISAM para cargas de trabajo serias.

    10
  5. 2018

    MySQL 8 reescribe los internos

    Llegan juntos un diccionario de datos transaccional, CTEs, funciones de ventana y un mejor soporte para JSON.

    18

La guia completa

MySQL: Todo lo que necesitas saber

¿Qué es MySQL?

MySQL es una base de datos relacional de código abierto que se convirtió en la capa de almacenamiento predeterminada de los inicios de la web. Fue lanzada en 1995 con un enfoque en la velocidad y la simplicidad para sitios con una alta carga de lectura, y creció junto al stack LAMP — Linux, Apache, MySQL y PHP — hasta convertirse en una de las bases de datos más desplegadas que existen.

Actualmente es desarrollada por Oracle, pero una gran comunidad y un amplio conjunto de servicios gestionados hacen que esté presente en todas partes. WordPress, Magento, plataformas de comercio electrónico estilo Shopify e innumerables aplicaciones personalizadas funcionan con MySQL. Si has utilizado un sitio web con un formulario de inicio de sesión, es muy probable que haya intervenido una tabla de MySQL.

El MySQL moderno no es el motor simple de los años 90. Desde la versión 8 cuenta con un diccionario de datos transaccional, common table expressions, window functions y un soporte capaz para JSON. Aprenderlo hoy significa aprender una base de datos relacional seria que, además, cuenta con un soporte extraordinario.

Dónde se ejecuta MySQL

La mayor ventaja práctica de MySQL es su ubicuidad. Casi todos los hostings compartidos, proveedores de nube y plataformas como servicio (PaaS) lo ofrecen. Los frameworks incluyen sus drivers, los ORM lo soportan de forma nativa y los DBAs cuentan con décadas de experiencia utilizándolo. Esto reduce el coste de todo lo que rodea a la base de datos: contratación, herramientas, monitoreo y migración.

Es la elección habitual para sistemas de gestión de contenidos, e-commerce, backends de SaaS y cualquier aplicación donde el ecosistema sea tan importante como el motor. Escala desde una única instancia pequeña hasta clusters con sharding, y el camino de una opción a la otra está ampliamente documentado.

InnoDB es el motor que importa

MySQL tiene una arquitectura de motores de almacenamiento enchufables, pero en la práctica utilizarás InnoDB. Este proporciona:

  • Transacciones ACID con COMMIT y ROLLBACK.
  • Bloqueo a nivel de fila (row-level locking), para que los escritores no bloqueen a los lectores.
  • Claves foráneas y cumplimiento de restricciones.
  • Recuperación ante fallos mediante un redo log.
  • MVCC para lecturas consistentes.

El antiguo motor MyISAM carecía de transacciones y de bloqueo a nivel de fila. No tiene cabida en un esquema nuevo. Declara siempre ENGINE=InnoDB explícitamente para que la elección sea visible y no dependa de los valores predeterminados del servidor.

CREATE TABLE products (
  id          BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  sku         VARCHAR(64) NOT NULL,
  name        VARCHAR(200) NOT NULL,
  price_cents INT UNSIGNED NOT NULL,
  stock       INT NOT NULL DEFAULT 0,
  attributes  JSON NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Tipos de datos y AUTO_INCREMENT

El sistema de tipos de MySQL es pragmático. Las opciones más comunes son:

  • INT y BIGINT — números enteros; añade UNSIGNED para IDs y contadores que no puedan ser negativos.
  • VARCHAR(n) — texto de longitud variable con un máximo. A diferencia de Postgres, en MySQL realmente conviene definir una longitud sensata.
  • TEXT — texto extenso que se almacena fuera de la fila cuando es necesario.
  • DECIMAL(p, s) — decimales exactos, el tipo correcto para dinero si no utilizas unidades menores en formato entero.
  • TIMESTAMP y DATETIME — marcas de tiempo; TIMESTAMP convierte a UTC y tiene un límite de rango, mientras que DATETIME almacena exactamente lo que le envíes.
  • JSON — un documento JSON validado y almacenado de manera eficiente.
  • ENUM — un conjunto fijo de cadenas; es conveniente, pero cambiar la lista requiere un cambio en el esquema.

AUTO_INCREMENT genera el siguiente entero para una columna, casi siempre la clave primaria. Es rápido y tolera huecos: los inserts que sufren un rollback consumen un valor, y los inserts concurrentes pueden no producir números contiguos. Nunca confíes en que el ID no tenga huecos ni en que su orden signifique algo.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 999, 10, JSON_OBJECT('colour', 'blue'));

SELECT LAST_INSERT_ID();

CRUD sin sorpresas

Las cuatro operaciones básicas se mapean a cuatro sentencias. Léelas como un conjunto, ya que la estructura se repite.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-2', 'Gadget', 1499, 5, JSON_OBJECT('colour', 'red'));

SELECT id, sku, name, price_cents
FROM products
WHERE stock > 0
ORDER BY price_cents ASC
LIMIT 20 OFFSET 40;

UPDATE products
SET price_cents = 1299, stock = stock - 1
WHERE id = 42;

DELETE FROM products
WHERE stock = 0 AND created_at < NOW() - INTERVAL 90 DAY;

Dos hábitos previenen los accidentes clásicos. Primero, incluye siempre una cláusula WHERE en UPDATE y DELETE; sin ella, todas las filas cambiarán. Segundo, ejecuta primero el SELECT equivalente para confirmar qué filas estás a punto de afectar. MySQL tiene un modo sql_safe_updates que rechaza sentencias sin una clave en el WHERE, y activarlo en desarrollo es una salvaguarda económica.

LIMIT con OFFSET pagina los resultados, pero los offsets grandes escanean y descartan filas. Para una paginación profunda, prefiere la paginación por claves (keyset pagination): WHERE id > :last_id ORDER BY id LIMIT 20.

Joins y agregación

Los joins combinan tablas basándose en una condición de coincidencia, exactamente igual que en el SQL estándar.

SELECT c.name             AS category,
       COUNT(*)           AS product_count,
       SUM(p.price_cents) AS inventory_value
FROM products p
JOIN categories c ON c.id = p.category_id
WHERE p.stock > 0
GROUP BY c.id, c.name
ORDER BY inventory_value DESC
LIMIT 20;

JOIN mantiene los pares coincidentes, LEFT JOIN mantiene todas las filas de la izquierda y rellena las columnas faltantes de la derecha con NULL. Los agregados como COUNT, SUM, AVG, MIN y MAX colapsan grupos; cada columna seleccionada que no esté agregada debe estar agrupada.

Históricamente, MySQL permitía seleccionar columnas no agrupadas y devolvía un valor arbitrario, lo que ocultaba errores. Con ONLY_FULL_GROUP_BY habilitado —el valor predeterminado en MySQL 8— el servidor rechaza las consultas ambiguas, que es lo ideal. No lo deshabilites para que una consulta antigua funcione; corrige la consulta.

WHERE filtra las filas antes de agrupar y HAVING filtra después, por lo que las condiciones sobre agregados pertenecen a HAVING.

Índices y EXPLAIN

Un índice es una estructura ordenada que evita el escaneo completo de una tabla. MySQL crea uno automáticamente para la clave primaria y para cada restricción UNIQUE. Añade otros para las columnas que utilices en filtros, joins y ordenamientos.

CREATE INDEX idx_products_price ON products (price_cents);

Un índice compuesto cubre varias columnas y sigue la regla del prefijo izquierdo: un índice en (category_id, price_cents) ayuda a las consultas que filtran por category_id, o por category_id y price_cents, pero no por price_cents solo. Ordena las columnas desde la más selectiva y la que se filtre con más frecuencia hacia afuera.

Pregunta al optimizador qué es lo que hará:

EXPLAIN
SELECT id, name, price_cents
FROM products
WHERE price_cents < 2500
ORDER BY price_cents
LIMIT 25;

Lee primero la columna type: const, eq_ref y ref son buenos; range está bien; index y ALL significan un escaneo. La columna key muestra qué índice fue seleccionado, y rows estima cuántos examinará. Un rows elevado para un resultado pequeño suele significar que falta un índice o que no se puede utilizar. Usa EXPLAIN ANALYZE en MySQL 8 para ejecutar la consulta y ver los tiempos reales.

Vale la pena mencionar los índices cubridores (covering indexes). Si un índice contiene cada columna que una consulta necesita, MySQL puede responder utilizando únicamente el índice sin tocar la fila. Añadir una columna a un índice puramente para convertirlo en un índice cubridor suele representar una mejora considerable.

utf8mb4 y la trampa del charset

Los conjuntos de caracteres son el punto donde MySQL suele sorprender a los desarrolladores. Durante la mayor parte de su historia, el utf8 predeterminado permitía un máximo de tres bytes por carácter, lo que cubre la mayoría de los textos pero no los puntos de código de cuatro bytes, como los emoji y muchos alfabetos poco comunes. Intentar almacenar un emoji en una columna utf8 provoca un error o trunca el valor, dependiendo del modo del servidor.

La solución es utf8mb4, que es UTF-8 real y lo almacena todo. Configúralo en todos los niveles: servidor, base de datos, tabla y conexión:

CREATE DATABASE shop
  CHARACTER SET utf8mb4
  COLLATE utf8mb4_0900_ai_ci;

La collation decide cómo se comparan y ordenan las cadenas. utf8mb4_0900_ai_ci es insensible a acentos y mayúsculas, que es generalmente lo que los usuarios esperan en una búsqueda. Las collations también afectan el comportamiento de los índices, así que mantenlas consistentes en las columnas que se unan mediante joins; una discrepancia fuerza conversiones que pueden hacer que un índice sea inutilizable.

Transacciones y niveles de aislamiento

InnoDB te ofrece transacciones. Agrupa escrituras relacionadas para que tengan éxito o fallen en conjunto.

START TRANSACTION;

UPDATE inventory SET quantity = quantity - 1
WHERE product_id = 42 AND quantity >= 1;

INSERT INTO orders (product_id, quantity)
VALUES (42, 1);

COMMIT;

Verifica las filas afectadas y ROLLBACK si la actualización protegida no cambió nada. El nivel de aislamiento predeterminado es Repeatable Read, el cual proporciona una instantánea consistente para la transacción y, en InnoDB, aplica gap locks que evitan las filas fantasma. Los otros niveles son Read Uncommitted, Read Committed y Serializable.

Una diferencia útil respecto a Postgres: debido al gap locking bajo Repeatable Read, las inserciones concurrentes en un rango pueden bloquearse o generar deadlocks con mayor facilidad. Mantén las transacciones cortas, actualiza las filas en un orden consistente y prepárate para reintentar en caso de un deadlock, el cual InnoDB reporta como un error en lugar de corromper el estado.

Replicación y escalado de lectura

La mayoría de los despliegues de MySQL escalan las lecturas antes que las escrituras. El primario registra cada cambio en su binary log, y una o más réplicas se conectan y reproducen dicho log. La replicación es asíncrona por defecto, por lo que una réplica puede tener un retraso (lag) respecto al primario de milisegundos o más.

El patrón estándar consiste en enviar las escrituras al primario y distribuir las lecturas entre las réplicas, aceptando que una lectura pueda devolver brevemente datos ligeramente desactualizados. Para lograr una consistencia de lectura posterior a la escritura (read-after-write consistency), redirige las lecturas de un usuario al primario durante un breve periodo después de su escritura, o lee de la réplica únicamente los datos que toleren el retraso.

La replicación también proporciona alta disponibilidad. Si el primario falla, una réplica puede ser promovida. Herramientas como los orquestadores y los servicios gestionados automatizan ese failover, pero aun así es necesario comprender la compensación entre los modos síncrono y asíncrono: el modo síncrono espera a las réplicas y pone en riesgo la disponibilidad, mientras que el asíncrono corre el riesgo de perder las últimas transacciones durante el failover.

Upserts con ON DUPLICATE KEY UPDATE

El upsert idiomático de MySQL es INSERT ... ON DUPLICATE KEY UPDATE. Cuando el insert violaría una clave primaria o única, se ejecuta en su lugar la cláusula de update.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES ('SKU-1', 'Widget', 1099, 5, JSON_OBJECT('colour', 'blue'))
ON DUPLICATE KEY UPDATE
  price_cents = VALUES(price_cents),
  stock       = stock + VALUES(stock);

Esto es atómico, lo cual es fundamental para contadores e inventarios. La alternativa —hacer un select y luego decidir si insertar o actualizar— presenta una ventana de condición de carrera (race condition) donde dos sesiones podrían no detectar la fila y ambas intentar insertar. Utiliza el upsert siempre que la operación sea genuinamente de insertar o actualizar.

Columnas JSON

El tipo JSON de MySQL almacena un documento validado y admite funciones para leer y escribir partes del mismo.

SELECT id, name,
       attributes->>'$.colour' AS colour
FROM products
WHERE attributes->>'$.colour' = 'blue';

Puedes indexar JSON mediante columnas generadas. MySQL no puede indexar una expresión JSON directamente, por lo que debes extraerla en una columna generada almacenada e indexar esa columna:

ALTER TABLE products
  ADD COLUMN colour VARCHAR(32)
    GENERATED ALWAYS AS (attributes->>'$.colour') STORED,
  ADD INDEX idx_products_colour (colour);

Utiliza JSON para atributos variables y payloads de terceros. Mantén los campos que filtres constantemente como columnas reales; el truco de la columna generada funciona, pero una columna nativa con un tipo real es más sencilla y rápida.

Procedimientos almacenados: úsalos con moderación

MySQL admite procedimientos almacenados, funciones, triggers y eventos programados. Estos pueden reducir los viajes de ida y vuelta (round trips) y centralizar la lógica, pero también trasladan la lógica de negocio a un lenguaje que es más difícil de testear, versionar y depurar que el código de tu aplicación.

Utilízalos para tareas que realmente pertenezcan al ámbito de la base de datos: mantenimiento masivo, migraciones de datos y trabajos que deban ejecutarse cerca de los datos. Evita colocar reglas fundamentales del dominio en procedimientos que solo un equipo comprenda. Los triggers son especialmente fáciles de olvidar; un UPDATE que dispara silenciosamente tres triggers es difícil de analizar, y los efectos secundarios ocultos acaban sorprendiendo a todo el mundo tarde o temprano.

Usuarios, permisos y respaldos

Crea un usuario de aplicación dedicado que tenga únicamente los privilegios necesarios, en lugar de conectarte como root.

CREATE USER 'app'@'%' IDENTIFIED BY 'a-strong-password';
GRANT SELECT, INSERT, UPDATE, DELETE ON shop.* TO 'app'@'%';
FLUSH PRIVILEGES;

Mantén el DDL y las migraciones en una cuenta separada y con más privilegios. Restringe los hosts siempre que sea posible, requiere TLS y rota las credenciales.

Para los respaldos, mysqldump genera un volcado lógico que es fácil de mover y restaurar:

mysqldump --single-transaction --routines --triggers shop > shop.sql
mysql shop_restore < shop.sql

El flag --single-transaction toma una instantánea consistente de las tablas InnoDB sin bloquearlas durante todo el volcado. Para bases de datos grandes donde el tiempo de volcado sea prohibitivo, utiliza una herramienta física como Percona XtraBackup. De cualquier forma, registra la posición del binary log para poder realizar una recuperación a un punto específico en el tiempo y prueba las restauraciones.

MySQL vs MariaDB

MariaDB comenzó como un fork comunitario de MySQL tras la adquisición por parte de Oracle y ha divergido desde entonces. Mantiene la mayor parte de la sintaxis de MySQL y añade sus propios motores de almacenamiento y funcionalidades. Por otro lado, MySQL ha avanzado rápidamente desde la versión 8 con un nuevo diccionario de datos, window functions y un soporte mejorado para JSON.

Para la mayoría de las aplicaciones, las diferencias son mínimas. Elige MySQL si tu proveedor gestionado o contrato de soporte se basan en él, y MariaDB si prefieres un proyecto gobernado por la comunidad o necesitas alguno de sus motores. Los conceptos relacionales, SQL y los patrones operativos son transferibles entre ambos, por lo que la elección rara vez es una decisión irreversible.

Búsqueda de texto completo en InnoDB

InnoDB incluye un índice de texto completo, por lo que una funcionalidad de búsqueda no requiere un servicio independiente. Crea un índice FULLTEXT sobre las columnas de texto y consúltalo con MATCH ... AGAINST.

CREATE FULLTEXT INDEX ft_products_search
  ON products (name, attributes);

SELECT id, name,
       MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE) AS score
FROM products
WHERE MATCH(name, attributes) AGAINST ('running shoe' IN NATURAL LANGUAGE MODE)
ORDER BY score DESC
LIMIT 20;

NATURAL LANGUAGE MODE clasifica por relevancia e ignora las palabras que aparecen en la mayoría de las filas. BOOLEAN MODE te ofrece operadores como +must, -exclude y "exact phrase" para tener un mayor control. El índice tiene una longitud mínima de token controlada por innodb_ft_min_token_size, por lo que es posible que se omitan las palabras muy cortas.

La búsqueda de texto completo es ideal para catálogos de productos y búsqueda de artículos. Cambia a un motor dedicado cuando necesites tolerancia a errores tipográficos, faceting o análisis multilingüe que MySQL no proporcione.

Vistas y columnas generadas

Una vista es una consulta con nombre que se comporta como una tabla, lo cual es útil para empaquetar una estructura común y para limitar qué columnas puede ver un usuario de reportes.

CREATE VIEW in_stock AS
SELECT id, sku, name, price_cents, stock
FROM products
WHERE stock > 0;

SELECT * FROM in_stock WHERE price_cents < 2500;

MySQL 8 soporta window functions, por lo que las vistas también pueden predefinir la forma de los resultados analíticos. Recuerda que una vista ejecuta su consulta subyacente cada vez que se llama, a menos que se materialice manualmente en una tabla; MySQL no tiene vistas materializadas nativas, por lo que los equipos suelen utilizar una tabla de resumen que se actualiza mediante un horario o un evento.

Las columnas generadas, detalladas en la sección de JSON, son la otra parte de este concepto: una columna generada almacenada (stored) se calcula al escribir y puede ser indexada, mientras que una virtual se calcula al leer. Utiliza una columna almacenada cuando necesites indexar la expresión, y una virtual cuando solo necesites el valor ocasionalmente.

Cambiar esquemas en una base de datos en producción

ALTER TABLE en una tabla InnoDB grande puede reconstruir la tabla y mantener bloqueos durante mucho tiempo. MySQL 8 soporta DDL online para muchas operaciones a través de ALGORITHM=INPLACE, que realiza la reconstrucción sin bloquear las lecturas y escrituras concurrentes en muchos casos.

ALTER TABLE products
  ADD COLUMN updated_at TIMESTAMP NULL,
  ALGORITHM=INPLACE, LOCK=NONE;

ALTER TABLE products
  ADD INDEX idx_products_updated (updated_at),
  ALGORITHM=INPLACE, LOCK=NONE;

No todos los cambios son online. Cambiar el tipo de una columna, añadir un índice FULLTEXT o reconstruir una clave primaria a menudo sigue implicando la copia de la tabla. Para estos casos, utiliza una herramienta como pt-online-schema-change o gh-ost, que crean una tabla espejo, copian las filas en lotes y realizan el intercambio con un bloqueo mínimo. Independientemente del método, ejecuta los cambios de esquema mediante migraciones versionadas y pruébalos primero en una copia de los datos de producción.

Cómo encontrar consultas lentas

MySQL registra las consultas que superan long_query_time en el slow query log, y EXPLAIN muestra cómo se ejecuta una consulta específica. Juntos, representan el camino más rápido para pasar de “la aplicación está lenta” a una solución concreta.

SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.2;

SHOW VARIABLES LIKE 'slow_query_log_file';

El performance_schema y el esquema sys resumen los mismos datos. Comienza con las sentencias que consumen más tiempo total, no con la consulta individual más lenta, y revisa EXPLAIN para detectar full scans en tablas grandes. SHOW PROFILE y EXPLAIN ANALYZE en MySQL 8 añaden tiempos por etapa cuando necesitas profundizar más.

Carga de datos rápida

Un bucle INSERT fila por fila es la forma más lenta de cargar datos. Agrupa varias filas en una sola sentencia; esto reduce los viajes de ida y vuelta (round trips) y permite que InnoDB escriba las páginas de manera eficiente.

INSERT INTO products (sku, name, price_cents, stock, attributes)
VALUES
  ('SKU-10', 'Widget', 999, 5, JSON_OBJECT('colour', 'blue')),
  ('SKU-11', 'Gadget', 1499, 3, JSON_OBJECT('colour', 'red')),
  ('SKU-12', 'Gizmo', 2499, 7, JSON_OBJECT('colour', 'green'));

Para importaciones masivas, LOAD DATA INFILE transmite un archivo directamente a una tabla y es drásticamente más rápido que cualquier forma de INSERT.

LOAD DATA LOCAL INFILE '/data/products.csv'
INTO TABLE products
FIELDS TERMINATED BY ',' ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(sku, name, price_cents, stock, @attributes)
SET attributes = CAST(@attributes AS JSON);

Envuelve las cargas grandes en una transacción para que un fallo no deje una tabla importada a medias. Considera desactivar los índices secundarios o las comprobaciones de unicidad durante una carga masiva puntual y reconstruirlos después. Para las escrituras cotidianas de la aplicación, un INSERT agrupado es la opción predeterminada correcta.

Mejores prácticas

  • Usa InnoDB para cada tabla y decláralo explícitamente.
  • Crea bases de datos, tablas y conexiones con utf8mb4 y una collation consistente.
  • Almacena el dinero como unidades menores enteras o DECIMAL, nunca como FLOAT o DOUBLE.
  • Añade índices para las consultas que realmente ejecutes y confírmalos con EXPLAIN.
  • Mantén el modo ONLY_FULL_GROUP_BY predeterminado y escribe consultas GROUP BY correctas.
  • Usa INSERT ... ON DUPLICATE KEY UPDATE para upserts atómicos en lugar de la estrategia check-then-act.
  • Mantén las transacciones cortas, actualiza las filas en un orden estable y reintenta en caso de deadlock.
  • Asigna a la aplicación un usuario con privilegios mínimos y mantén el DDL en una cuenta de migraciones.
  • Realiza backups consistentes con --single-transaction, registra la posición del binlog y prueba las restauraciones.
  • Prefiere la paginación por keyset sobre los escaneos extensos de LIMIT ... OFFSET.

Errores comunes

  • Dejar tablas en MyISAM y perder las transacciones y el bloqueo a nivel de fila.
  • Usar el antiguo utf8 de tres bytes y luego preguntarse por qué fallan los emoji al guardarse.
  • Almacenar dinero en FLOAT y acumular errores de redondeo.
  • Ejecutar UPDATE o DELETE sin un WHERE y reescribir la tabla completa.
  • Indexar cada columna en lugar de las consultas que realmente importan, lo que ralentiza las escrituras.
  • Confiar en que AUTO_INCREMENT sea continuo o tenga un significado.
  • Hacer operaciones de lectura-modificación-escritura en el código de la aplicación en lugar de un upsert atómico.
  • Leer de una réplica inmediatamente después de una escritura y ver datos obsoletos.
  • Desactivar ONLY_FULL_GROUP_BY para ocultar una agregación incorrecta.
  • Permitir que la aplicación se conecte como root.

Próximos pasos

MySQL y sus conceptos son muy versátiles. Si quieres comparar con el otro motor relacional principal, lee la guía de PostgreSQL, que cubre los mismos conceptos pero con diferentes valores predeterminados. Para profundizar en el lenguaje que sustenta a ambos, la guía de SQL explica los joins, CTEs y window functions desde los principios básicos. Y dado que la mayoría de los despliegues de MySQL se apoyan en una caché, Redis y el modelo de documentos de MongoDB son complementos naturales.

En la practica

Esquema, consulta, plan, upsert

Cuatro tareas cotidianas de MySQL: definir una tabla InnoDB, hacer un join, inspeccionar un plan y realizar un upsert de forma segura.

schema.sql
CREATE TABLE categories (
  id   INT UNSIGNED NOT NULL AUTO_INCREMENT,
  name VARCHAR(80) NOT NULL,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_categories_name (name)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE products (
  id           BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  category_id  INT UNSIGNED NOT NULL,
  sku          VARCHAR(64) NOT NULL,
  name         VARCHAR(200) NOT NULL,
  price_cents  INT UNSIGNED NOT NULL,
  stock        INT NOT NULL DEFAULT 0,
  attributes   JSON NOT NULL,
  created_at   TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (id),
  UNIQUE KEY uniq_products_sku (sku),
  KEY idx_products_category (category_id),
  CONSTRAINT fk_products_category
    FOREIGN KEY (category_id) REFERENCES categories (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Escribir un upsert

La base de datos puede manejar la inserción o actualización atómicamente. Verificar primero y luego insertar deja una condición de carrera entre las dos sentencias.

Preferido
INSERT INTO products (sku, name, price_cents, stock)
VALUES ('SKU-1', 'Widget', 999, 5)
ON DUPLICATE KEY UPDATE
  stock = stock + VALUES(stock);
Evitar
SELECT id FROM products WHERE sku = 'SKU-1';
-- another session can insert the same
-- SKU between these two statements
INSERT INTO products (sku, name, price_cents, stock)
VALUES ('SKU-1', 'Widget', 999, 5);

Elegir un charset

utf8mb4 es el único charset que almacena cada carácter Unicode, incluyendo emojis. El antiguo utf8 es una trampa.

Preferido
CREATE TABLE notes (
  id   BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  body TEXT NOT NULL,
  PRIMARY KEY (id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
  COLLATE=utf8mb4_0900_ai_ci;
Evitar
CREATE TABLE notes (
  id   BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
  body TEXT NOT NULL,
  PRIMARY KEY (id)
) DEFAULT CHARSET=utf8;
-- utf8 here is a 3-byte subset: emoji
-- are silently rejected or mangled

Compromisos

¿Es MySQL la base de datos relacional adecuada para ti?

MySQL está probado en batalla y es universalmente soportado. Las desventajas se centran principalmente en los valores predeterminados y el dialecto.

Strengths

  • Probado a escala web

    Ha impulsado aplicaciones web con alta lectura durante décadas, y el manual operativo para replicación y failover es maduro.

  • Presente en todas partes

    Frameworks, ORMs, hosts y DBAs conocen MySQL, por lo que la incorporación y contratación son sencillas y las opciones gestionadas abundan.

  • Escalado de lectura simple

    La replicación asíncrona hace que sea sencillo añadir réplicas y rutear las consultas de lectura hacia ellas sin cambiar el esquema.

Trade-offs

  • Los predeterminados pueden traicionarte

    Los servidores antiguos aún usan configuraciones y charsets más débiles, y Repeatable Read se comporta diferente a Postgres, así que revisa la configuración en lugar de confiar ciegamente.

  • El dialecto es muy particular

    Los upserts, operadores JSON y funciones son específicos de MySQL. El SQL escrito aquí rara vez se traslada a otro motor sin cambios.

  • Acumulación de funcionalidades

    Los procedimientos almacenados, triggers y eventos son potentes y fáciles de usar en exceso, convirtiendo la lógica de negocio en código de base de datos difícil de testear.

Preguntas frecuentes

Preguntas frecuentes

Keep learning

Related topics from the roadmap.

$ comienza a aprender

Listo para aprender MySQL?

Nuestro tutorial interactivo te guia a traves de MySQL paso a paso — con quizzes y codigo real que puedes ejecutar en el navegador.