--- title: "MySQL vs PostgreSQL: diferencias y cuál elegir en 2026" description: "Compara MySQL y PostgreSQL en arquitectura, SQL, JSON, replicación y conexiones, con ejemplos prácticos en Ubuntu 24.04 para elegir la base de datos adecuada." category: "Guía de Comparación" language: es source: https://cubepath.com/es/docs/guía-de-comparación/mysql-vs-postgresql-cual-elegir updated: 2026-09-25 --- # MySQL vs PostgreSQL: diferencias y cuál elegir en 2026 MySQL y PostgreSQL son las dos bases de datos relacionales de código abierto más usadas. Las dos son fiables, soportan transacciones ACID y funcionan bien con cualquier framework moderno, pero difieren en el modelo de conexiones, en lo estrictas que son con los datos, en funciones SQL avanzadas y en cómo se replican. En esta guía verás esas diferencias con ejemplos que puedes ejecutar en Ubuntu 24.04 y un criterio práctico para elegir. ## Resumen rápido | Aspecto | MySQL (InnoDB) | PostgreSQL | |---|---|---| | Versión en Ubuntu 24.04 | 8.0 (paquete `mysql-server`) | 16 (paquete `postgresql`) | | Modelo de conexiones | Un hilo por conexión | Un proceso por conexión | | Aislamiento por defecto | `REPEATABLE READ` | `READ COMMITTED` | | Comparación de texto por defecto | Insensible a mayúsculas (`utf8mb4_0900_ai_ci`) | Sensible a mayúsculas | | DDL dentro de transacciones | No, cada `ALTER` hace commit implícito | Sí, se puede deshacer con `ROLLBACK` | | JSON | Tipo `JSON`, índices sobre columnas generadas | `JSON` y `JSONB` con índices GIN | | Vistas materializadas | No | Sí | | `RETURNING` | No | Sí | | Extensiones | Plugins limitados | PostGIS, pg_trgm, TimescaleDB, pgvector y muchas más | | Replicación | Binlog asíncrona o semisíncrona, Group Replication | Streaming física y replicación lógica | | Mantenimiento | Mínimo | `VACUUM` (automático con autovacuum) | Oracle dio por terminado el soporte de MySQL 8.0 en abril de 2026 y la rama LTS actual es la 8.4. Las diferencias de esta guía aplican igual a ambas. MariaDB es un fork de MySQL con alta compatibilidad; Ubuntu 24.04 incluye MariaDB 10.11. Casi todo lo que se dice aquí de MySQL aplica también a MariaDB, aunque MariaDB sí soporta `RETURNING` en `INSERT` y `DELETE`. ## Requisitos previos Para probar los ejemplos necesitas: - Un servidor con Ubuntu 24.04 LTS, por ejemplo un VPS de CubePath, con al menos 2 GB de RAM si instalas los dos motores a la vez. - Un usuario no root con privilegios `sudo`. Instala ambos desde los repositorios de Ubuntu: ```bash sudo apt update sudo apt install mysql-server postgresql ``` Comprueba las versiones: ```bash mysql --version psql --version ``` ```output mysql Ver 8.0.43-0ubuntu0.24.04.1 for Linux on x86_64 ((Ubuntu)) psql (PostgreSQL) 16.10 (Ubuntu 16.10-0ubuntu0.24.04.1) ``` Los números de parche serán distintos en tu servidor. Crea una base de datos de pruebas en cada motor: ```bash sudo mysql -e "CREATE DATABASE pruebas;" sudo -u postgres createdb pruebas ``` Abre una consola en cada uno con `sudo mysql pruebas` y `sudo -u postgres psql pruebas`. ## Arquitectura y conexiones MySQL crea un hilo por conexión dentro de un único proceso `mysqld`. Aguanta cientos o miles de conexiones con un coste de memoria moderado, lo que encaja con aplicaciones PHP que abren una conexión por petición. PostgreSQL crea un proceso del sistema operativo por conexión. Cada proceso es más pesado, y por encima de unos pocos cientos de conexiones activas el rendimiento cae. La solución estándar es poner un pool de conexiones delante, como PgBouncer: ```bash sudo apt install pgbouncer ``` Comprueba el límite configurado en cada motor: ```bash sudo mysql -e "SHOW VARIABLES LIKE 'max_connections';" sudo -u postgres psql -c "SHOW max_connections;" ``` ```output +-----------------+-------+ | Variable_name | Value | +-----------------+-------+ | max_connections | 151 | +-----------------+-------+ max_connections ----------------- 100 (1 row) ``` Si tu aplicación usa un pool propio (la mayoría de frameworks en Java, Go, Python o Node.js lo hacen), esta diferencia importa mucho menos. ## Diferencias de comportamiento que afectan a tu aplicación ### Mayúsculas y minúsculas en comparaciones Con la collation por defecto de MySQL 8.0, `'Ana' = 'ana'` es verdadero. En PostgreSQL es falso. Pruébalo en cada consola: ```sql SELECT 'Ana' = 'ana'; ``` MySQL devuelve `1` y PostgreSQL devuelve `f`. Esto afecta a restricciones `UNIQUE` sobre emails o nombres de usuario: en PostgreSQL se suele resolver con un índice sobre `lower(email)` o con la extensión `citext`. ### DDL transaccional En PostgreSQL una migración fallida se puede deshacer entera: ```sql BEGIN; CREATE TABLE clientes (id serial PRIMARY KEY, email text NOT NULL); ALTER TABLE clientes ADD COLUMN pais char(2); ROLLBACK; ``` Tras el `ROLLBACK` la tabla no existe. En MySQL cada `CREATE` o `ALTER` hace commit implícito, así que una migración que falla a mitad deja la base de datos en un estado intermedio que hay que arreglar a mano. ### Insertar o actualizar (upsert) La sintaxis es distinta. En MySQL: ```sql CREATE TABLE contadores (clave varchar(50) PRIMARY KEY, valor int NOT NULL); INSERT INTO contadores (clave, valor) VALUES ('visitas', 1) ON DUPLICATE KEY UPDATE valor = valor + 1; ``` En PostgreSQL, que además devuelve la fila con `RETURNING`: ```sql CREATE TABLE contadores (clave varchar(50) PRIMARY KEY, valor int NOT NULL); INSERT INTO contadores (clave, valor) VALUES ('visitas', 1) ON CONFLICT (clave) DO UPDATE SET valor = contadores.valor + 1 RETURNING valor; ``` PostgreSQL 15 y posteriores también soportan `MERGE` del estándar SQL. ## SQL avanzado Desde MySQL 8.0 la distancia se ha reducido mucho. Ambos soportan: - Expresiones de tabla comunes (`WITH`), incluidas las recursivas. - Funciones de ventana (`ROW_NUMBER()`, `RANK()`, `LAG()`, `SUM() OVER (...)`). - Restricciones `CHECK` (MySQL desde 8.0.16). Esta consulta funciona igual en los dos: ```sql WITH ventas AS ( SELECT 'norte' AS zona, 120 AS importe UNION ALL SELECT 'norte', 80 UNION ALL SELECT 'sur', 200 ) SELECT zona, importe, SUM(importe) OVER (PARTITION BY zona) AS total_zona FROM ventas; ``` Lo que solo tiene PostgreSQL: - Vistas materializadas (`CREATE MATERIALIZED VIEW` y `REFRESH MATERIALIZED VIEW CONCURRENTLY`). - Índices parciales (`CREATE INDEX ... WHERE activo`) e índices de expresión sobre cualquier función. - Tipos nativos de arrays, rangos (`tstzrange`), `inet`/`cidr` y tipos definidos por el usuario. - Restricciones de exclusión, por ejemplo para impedir reservas que se solapen en el tiempo. - Un ecosistema de extensiones que se activan con `CREATE EXTENSION`. ## JSON Los dos almacenan JSON validado, pero PostgreSQL lo indexa de forma más flexible. En PostgreSQL con `JSONB` y un índice GIN puedes buscar por cualquier clave: ```sql CREATE TABLE eventos (id bigserial PRIMARY KEY, datos jsonb NOT NULL); CREATE INDEX eventos_datos_idx ON eventos USING gin (datos); INSERT INTO eventos (datos) VALUES ('{"tipo": "login", "usuario": 42}'); SELECT id FROM eventos WHERE datos @> '{"tipo": "login"}'; ``` En MySQL no se indexa la columna `JSON` directamente: se crea una columna generada con la ruta concreta y se indexa esa columna: ```sql CREATE TABLE eventos ( id bigint AUTO_INCREMENT PRIMARY KEY, datos json NOT NULL, tipo varchar(20) AS (JSON_UNQUOTE(JSON_EXTRACT(datos, '$.tipo'))) STORED, INDEX (tipo) ); INSERT INTO eventos (datos) VALUES ('{"tipo": "login", "usuario": 42}'); SELECT id FROM eventos WHERE tipo = 'login'; ``` Funciona bien cuando sabes de antemano qué claves vas a consultar. Si las consultas sobre JSON son variadas, PostgreSQL es claramente mejor. ## Replicación y alta disponibilidad **MySQL** replica mediante el binary log. Una réplica se configura con `CHANGE REPLICATION SOURCE TO` y `START REPLICA`, y puede ser asíncrona o semisíncrona. Para alta disponibilidad con failover automático existe Group Replication, que junto con MySQL Router y MySQL Shell forma InnoDB Cluster. **PostgreSQL** ofrece replicación física por streaming (una copia exacta del servidor, creada con `pg_basebackup`) y replicación lógica por publicaciones y suscripciones, útil para replicar solo algunas tablas o migrar entre versiones mayores. El failover automático no viene incluido: se usa una herramienta externa como Patroni. Ambos soportan réplicas de lectura de forma fiable. La diferencia está en las herramientas: MySQL trae más piezas oficiales y PostgreSQL depende más del ecosistema. ## Mantenimiento: VACUUM en PostgreSQL PostgreSQL implementa MVCC guardando versiones antiguas de las filas en la propia tabla. `VACUUM` recupera ese espacio y el proceso autovacuum lo ejecuta automáticamente. En tablas con muchas actualizaciones conviene vigilarlo: ```sql SELECT relname, n_dead_tup, last_autovacuum FROM pg_stat_user_tables ORDER BY n_dead_tup DESC LIMIT 5; ``` Si `n_dead_tup` crece sin parar y `last_autovacuum` está vacío o es muy antiguo, hay que ajustar autovacuum para esa tabla. InnoDB gestiona las versiones antiguas en el undo log y no requiere esta atención. ## Ajuste inicial El parámetro más importante de cada motor es la memoria de caché. En MySQL, crea `/etc/mysql/mysql.conf.d/zz-tuning.cnf`. En un servidor dedicado se suele dar entre el 50 y el 70 % de la RAM al buffer pool; con 4 GB: ```ini [mysqld] innodb_buffer_pool_size = 2G ``` En PostgreSQL 16, edita `/etc/postgresql/16/main/postgresql.conf`. La recomendación habitual es un 25 % de la RAM para `shared_buffers`: ```ini shared_buffers = 1GB effective_cache_size = 3GB ``` Reinicia cada servicio y comprueba que el valor se aplicó: ```bash sudo systemctl restart mysql postgresql sudo mysql -e "SELECT @@innodb_buffer_pool_size/1024/1024/1024 AS gb;" sudo -u postgres psql -c "SHOW shared_buffers;" ``` ## Cuál elegir **Elige MySQL cuando:** - Usas software que lo trae como opción principal: WordPress, Magento, PrestaShop, Nextcloud o muchos paneles de hosting. - La carga es sobre todo de lecturas simples por clave primaria y muchas conexiones cortas. - Tu equipo ya lo opera y conoce sus herramientas de replicación. **Elige PostgreSQL cuando:** - Empiezas un proyecto nuevo con Django, Rails, Laravel, Spring o Node.js y no hay una razón concreta para MySQL. - Necesitas JSON consultable, datos geográficos (PostGIS), búsqueda de texto con `pg_trgm`, vectores con `pgvector` o series temporales. - Te importa la integridad: DDL transaccional, restricciones avanzadas y un comportamiento más estricto con los tipos. **Evalúa con más calma cuando:** - Migras una aplicación grande: el coste está en reescribir consultas, collations y procedimientos almacenados, no en el motor. - Vas a depender de un servicio gestionado concreto: revisa qué versiones y extensiones ofrece. ## Conclusión Para un proyecto nuevo sin restricciones, PostgreSQL es la opción más completa y la que menos se queda corta cuando la aplicación crece. MySQL sigue siendo la elección natural para el ecosistema PHP y las aplicaciones que ya lo usan, y con la versión 8 ofrece CTEs y funciones de ventana. Como siguientes pasos, configura copias de seguridad con `mysqldump` o `pg_dump`, protege el acceso remoto con el firewall y prueba tu aplicación real sobre el motor elegido antes de decidir por benchmarks genéricos.