Conectar a MySQL
Cómo conectar a MySQL en Python, JavaScript y Java — con pool de conexiones, SSL y consultas preparadas que aguantan en producción.
Visión General
Conectar a MySQL traba a la gente siempre en los mismos tres puntos: elegir el driver correcto, configurar bien SSL y autenticación (MySQL 8 cambió el plugin de autenticación por defecto) y usar un pool de conexiones para que el servidor no llegue al max_connections. Aquí tienes una configuración que funciona en Python, JavaScript y Java, lista para copiar y adaptar.
Cuándo Usar
Usa este recurso cuando:
- Construyes una app o API cuyo almacén principal es MySQL 8.x (o MariaDB 10.x, que comparte el protocolo y la mayoría de drivers)
- Escribes scripts que importan o exportan datos a través de una conexión MySQL
- Migras un código a otro driver o añades un pool de conexiones a código que abre una conexión por consulta
- Apuntas un servicio a una instancia gestionada como AWS RDS, Aurora o Cloud SQL
Si necesitas almacenamiento de documentos, escritura multi-región o consultas JSON más profundas, MySQL no es la opción por defecto — consulta Conectar a PostgreSQL como la alternativa más cercana.
Solución
Python
import os
import mysql.connector
conn = mysql.connector.connect(
host=os.getenv("DB_HOST", "localhost"),
database=os.getenv("DB_NAME", "mydb"),
user=os.getenv("DB_USER", "user"),
password=os.environ["DB_PASSWORD"],
ssl_ca="ca.pem", # obligatorio en producción
ssl_verify_cert=True,
connection_timeout=5,
charset="utf8mb4"
)
cursor = conn.cursor(dictionary=True)
cursor.execute("SELECT * FROM users WHERE id = %s", (1,))
row = cursor.fetchone()
cursor.close()
conn.close()
JavaScript
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: process.env.DB_HOST || 'localhost',
database: process.env.DB_NAME || 'mydb',
user: process.env.DB_USER || 'user',
password: process.env.DB_PASSWORD,
ssl: { ca: require('fs').readFileSync('ca.pem') },
connectionLimit: 10,
waitForConnections: true,
queueLimit: 0,
connectTimeout: 10000
});
async function getUser(id) {
const [rows] = await pool.execute('SELECT * FROM users WHERE id = ?', [id]);
return rows[0];
}
Java
import java.sql.*;
public class MySQLConnect {
public Connection connect() throws SQLException {
String url = "jdbc:mysql://localhost:3306/mydb"
+ "?sslMode=VERIFY_CA&serverTimezone=UTC"
+ "&connectTimeout=5000&socketTimeout=30000";
return DriverManager.getConnection(url, "user", "pass");
}
public void queryUser(int id) throws SQLException {
try (Connection conn = connect();
PreparedStatement stmt = conn.prepareStatement("SELECT * FROM users WHERE id = ?")) {
stmt.setInt(1, id);
ResultSet rs = stmt.executeQuery();
while (rs.next()) {
System.out.println(rs.getString("email"));
}
}
}
}
Explicación
Los tres ejemplos pasan la entrada del usuario por consultas preparadas (%s en Python, ? en mysql2 y JDBC), así que el driver escapa los valores y la inyección SQL queda descartada. Cada llamada a mysql.connector.connect() abre un socket TCP nuevo y autentica — vale para un script, pero es demasiado lento por petición; los ejemplos con pool de más abajo lo resuelven. El pool de mysql2 mantiene vivo un conjunto fijo de conexiones y encola el trabajo cuando todas están ocupadas. La versión Java usa el com.mysql.cj.jdbc.Driver moderno (Connector/J 8.x), que lee serverTimezone y sslMode directamente de la URL JDBC.
Dos detalles que queman a la gente más tarde. El utf8 de MySQL es una codificación de 3 bytes, no UTF-8 real — usa utf8mb4 (como en el ejemplo de Python, charset: 'utf8mb4' en mysql2 o characterEncoding=UTF-8 en JDBC) o los emojis y los caracteres de 4 bytes se corrompen sin avisar. Y wait_timeout vale 28800 segundos por defecto en el servidor: las conexiones inactivas del pool se mueren debajo de ti, así que dale al pool un timeout de inactividad más corto o keepalive (ver Consejos de Producción).
Una vez conectado, puedes ejecutar SQL en crudo o pasar a un ORM para operaciones CRUD.
Lo que funciona
- Dimensiona el pool para que
connectionLimit× instancias de la app quede por debajo delmax_connectionsdel servidor (151 por defecto), dejando margen para sesiones de administración - Verifica el certificado del servidor en producción (
ssl_verify_cert=True,sslMode=VERIFY_CA);sslmode=requirecifra pero no comprueba la identidad - Dale al pool un timeout de inactividad más corto que el
wait_timeoutde MySQL, o las conexiones inactivas vuelven muertas en la siguiente consulta - Usa
execute()/ consultas preparadas para cualquier dato del usuario; concatenar enquery()es como ocurren las inyecciones - Guarda las credenciales en variables de entorno o un gestor de secretos — en AWS RDS puedes omitir la contraseña con autenticación IAM de base de datos
Errores Comunes
- Instalar el paquete
mysqldeprecado en Node.js — lleva años sin mantenimiento; usamysql2 - Olvidar
serverTimezone=UTCen la URL JDBC, que desplaza los valoresTIMESTAMPsegún la zona horaria local de la JVM - Abrir una conexión nueva dentro de un bucle de peticiones en vez de pedirla al pool — llegarás a “Too many connections” (error 1040)
- Dejar que el pool sobreviva al
wait_timeouty luego servir erroresPROTOCOL_CONNECTION_LOSTa los usuarios - Asumir que
utf8es suficiente — guarda como máximo 3 bytes por carácter; necesitasutf8mb4
Ejemplos Avanzados
Python con pool de conexiones y gestor de contexto
import mysql.connector
from mysql.connector import pooling
from contextlib import contextmanager
mysql_pool = pooling.MySQLConnectionPool(
pool_name="mypool",
pool_size=10,
host="localhost",
database="mydb",
user="user",
password="pass",
ssl_ca="ca.pem",
ssl_verify_cert=True
)
@contextmanager
def get_db_cursor():
conn = mysql_pool.get_connection()
try:
cursor = conn.cursor(dictionary=True)
yield cursor
conn.commit()
cursor.close()
except Exception:
conn.rollback()
raise
finally:
conn.close() # devuelve la conexión al pool
# Uso
with get_db_cursor() as cur:
cur.execute("SELECT * FROM users WHERE active = %s", (True,))
rows = cur.fetchall()
for row in rows:
print(row)
JavaScript con manejo de transacciones
const mysql = require('mysql2/promise');
const pool = mysql.createPool({
host: 'localhost',
database: 'mydb',
user: 'user',
password: 'pass',
ssl: { ca: require('fs').readFileSync('ca.pem') },
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0,
enableKeepAlive: true,
keepAliveInitialDelay: 10000
});
async function transferBalance(fromId, toId, amount) {
const conn = await pool.getConnection();
try {
await conn.beginTransaction();
await conn.execute(
'UPDATE accounts SET balance = balance - ? WHERE id = ?',
[amount, fromId]
);
await conn.execute(
'UPDATE accounts SET balance = balance + ? WHERE id = ?',
[amount, toId]
);
await conn.commit();
} catch (err) {
await conn.rollback();
throw err;
} finally {
conn.release();
}
}
Java con pool HikariCP
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import java.sql.*;
public class MySQLPool {
private static final HikariDataSource ds;
static {
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydb?sslMode=VERIFY_CA&serverTimezone=UTC&rewriteBatchedStatements=true");
config.setUsername("user");
config.setPassword("pass");
config.setMaximumPoolSize(20);
config.setMinimumIdle(5);
config.setIdleTimeout(30000);
config.setConnectionTimeout(5000);
config.setKeepaliveTime(300000); // refresca conexiones antes de que wait_timeout las mate
ds = new HikariDataSource(config);
}
public static Connection getConnection() throws SQLException {
return ds.getConnection();
}
public static void batchInsert(List<String> emails) throws SQLException {
try (Connection conn = getConnection();
PreparedStatement stmt = conn.prepareStatement(
"INSERT INTO users (email) VALUES (?)")) {
for (String email : emails) {
stmt.setString(1, email);
stmt.addBatch();
}
stmt.executeBatch(); // rápido gracias a rewriteBatchedStatements=true
}
}
}
Python con PyMySQL (alternativa en Python puro)
import pymysql
from contextlib import contextmanager
@contextmanager
def get_db():
conn = pymysql.connect(
host="localhost",
database="mydb",
user="user",
password="pass",
ssl={"ca": "ca.pem"},
charset="utf8mb4",
cursorclass=pymysql.cursors.DictCursor
)
try:
yield conn
conn.commit()
except Exception:
conn.rollback()
raise
finally:
conn.close()
# Uso
with get_db() as conn:
with conn.cursor() as cur:
cur.execute("SELECT * FROM users WHERE id = %s", (1,))
row = cur.fetchone()
print(row)
Variantes
| Tecnología | Driver | Async | Pooling | Notas |
|---|---|---|---|---|
| Python | mysql-connector-python | No | MySQLConnectionPool | Driver oficial de Oracle |
| Python | PyMySQL | No | Manual | Python puro, se instala sin extensiones C |
| Python | aiomysql | Sí | Integrado | MySQL asíncrono para asyncio |
| JavaScript | mysql2 | Sí (Promise) | createPool | Recomendado para Node.js |
| JavaScript | mysql | No (callback) | createPool | Driver legacy con callbacks — evítalo en código nuevo |
| Java | JDBC + HikariCP | No | HikariCP | Estándar de la industria — opción de pooling para Spring Boot |
| Go | go-sql-driver/mysql | Sí | database/sql | Driver estándar de Go |
Consejos de Producción
- Activa
enableKeepAliveen mysql2. Las conexiones inactivas las descarta el servidor (wait_timeout, 28800s por defecto) o los equipos de red intermedios; las sondas keepalive evitan que el pool te entregue un socket muerto:
const pool = mysql.createPool({
// ... otras opciones
enableKeepAlive: true,
keepAliveInitialDelay: 10000
});
- Usa
LOAD DATA INFILEpara cargas masivas. Es órdenes de magnitud más rápido queINSERTfila a fila para datasets grandes; tanto el servidor como el driver cliente necesitanlocal_infileactivado:
LOAD DATA LOCAL INFILE '/tmp/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
LINES TERMINATED BY '\n'
IGNORE 1 ROWS
(id, email, name);
- Ajusta
wait_timeouteinteractive_timeouten el servidor. Bájalos para reciclar antes las conexiones inactivas, o súbelos a propósito si el pool mantiene conexiones abiertas — en cualquier caso, mantén el timeout de inactividad del pool por debajo del del servidor:
SET GLOBAL wait_timeout = 300;
SET GLOBAL interactive_timeout = 300;
- Usa autenticación IAM en AWS RDS. Sustituye contraseñas guardadas por tokens de autenticación de 15 minutos, generados bajo demanda (hace falta SSL para usarlos):
aws rds generate-db-auth-token \
--hostname mydb.abc123.us-east-1.rds.amazonaws.com \
--port 3306 \
--username myuser
- Vigila
SHOW PROCESSLISTpara detectar consultas fugadas. Muestra lo que hace cada conexión en este momento, para localizar y matar la consulta que bloquea al resto:
SHOW PROCESSLIST;
KILL <thread_id>;
Solución de Problemas
| Error | Qué significa | Cómo arreglarlo |
|---|---|---|
ECONNREFUSED | No hay nada escuchando en host:puerto, o un firewall/grupo de seguridad lo bloquea | Comprueba que mysqld está arriba, el puerto 3306 abierto y el host cliente permitido |
ETIMEDOUT / PROTOCOL_CONNECTION_LOST | Caída de red, o el servidor mató una conexión inactiva en wait_timeout | Activa keepalive en el pool y pon su timeout de inactividad por debajo de wait_timeout |
ER_ACCESS_DENIED_ERROR (1045) | Contraseña incorrecta, o no hay permiso para ese par usuario@host | La autenticación MySQL es por usuario+host — recrea el permiso para la IP/host real del cliente |
ER_BAD_DB_ERROR (1049) | El nombre de la base de datos no existe (o el usuario no puede verla) | Crea el esquema o corrige el nombre en la configuración de conexión |
Too many connections (1040) | max_connections (151 por defecto) agotado | Reduce los pools entre instancias de la app, o sube max_connections |
Authentication plugin 'caching_sha2_password' cannot be loaded | Un cliente/driver antiguo contra MySQL 8+ | Actualiza el driver; como último recurso, ALTER USER ... IDENTIFIED WITH mysql_native_password (deprecado, desactivado por defecto en 8.4) |
| Errores de certificado SSL | Bundle CA ausente o desactualizado | Apunta ssl_ca / sslMode=VERIFY_CA a un bundle CA actual |
Ver también
- Guía de desarrollo de MySQL Connector/Python — opciones de conexión, pooling y flags SSL
- Documentación de node-mysql2 — opciones del pool, consultas preparadas y keepalive
- HikariCP — dimensionado del pool y ajustes de keepalive para Java
- AWS: autenticación IAM de base de datos para RDS — logins por token sin contraseñas guardadas
Preguntas frecuentes
¿Debo usar mysql-connector-python o PyMySQL?
mysql-connector-python es el driver oficial de Oracle y la opción por defecto más segura. Recurre a PyMySQL cuando no puedas instalar extensiones C o cuando topes con un caso límite del driver de Oracle — ambos aceptan los mismos placeholders %s, así que el cambio es pequeño.
¿Cómo manejo los timeouts de conexión en mysql2?
Configura connectTimeout en las opciones del pool — mysql2 no implementa acquireTimeout ni timeout, que eran del paquete legacy mysql. Para pools de larga duración, combina connectTimeout con enableKeepAlive y keepAliveInitialDelay para que los sockets muertos se refresquen en lugar de servirse a las consultas.
¿Cuál es la diferencia entre query() y execute() en mysql2?
query() envía el SQL como texto plano. execute() emite una consulta preparada con parámetros vinculados — segura contra inyección SQL y algo más rápida en llamadas repetidas porque el servidor reutiliza el plan de ejecución.
¿Cómo manejo los fallos de conexión con elegancia?
Envuelve las llamadas en lógica de reintento con backoff exponencial y un tope de intentos — tenacity en Python, p-retry en Node.js:
from tenacity import retry, stop_after_attempt, wait_exponential
@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=1, max=10))
def query_with_retry(sql, params):
with get_db_cursor() as cur:
cur.execute(sql, params)
return cur.fetchall()
¿Cómo activo el log de consultas para depurar?
Activa el slow query log para capturar todo lo que supere un umbral:
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 0.1;
SET GLOBAL slow_query_log_file = '/var/log/mysql/slow.log';
Después inspecciona las consultas marcadas con EXPLAIN; si el plan muestra escaneos completos en tablas grandes, consulta la guía de estrategias de indexación.
¿Qué es el multiplexing de conexiones en MySQL?
ProxySQL y MySQL Router se sitúan entre la app y la base de datos, manteniendo muchas sesiones de cliente sobre un conjunto menor de conexiones reales al servidor. Importa cuando tienes cientos de instancias de app contra un max_connections de 151. En ProxySQL, transaction_persistent=0 es el ajuste que la mayoría de apps quieren.
¿Cómo uso SSL con MySQL en AWS RDS?
Descarga el bundle CA actual de RDS (bundles por región como rds-ca-rsa2048-g1, o global-bundle.pem para todas las regiones — el viejo bundle 2019 ya está rotado) y apúntalo en el driver:
conn = mysql.connector.connect(
host="mydb.abc123.us-east-1.rds.amazonaws.com",
database="mydb",
user="user",
password="pass",
ssl_ca="rds-ca-rsa2048-g1.pem",
ssl_verify_cert=True
)
Recursos Relacionados
Conectar a PostgreSQL
Cómo conectar a bases de datos PostgreSQL en Python, JavaScript y Java.
RecipeConectar a Redis
Cómo conectar a Redis en Python, JavaScript y Java — con AUTH, TLS, pooling y los tipos de datos que realmente usarás.
RecipeEjecutar SQL Crudo
Cómo ejecutar consultas SQL crudas de forma segura con sentencias parametrizadas.
RecipeCRUD con ORM: SQLAlchemy, Prisma y Hibernate
Cómo ejecutar operaciones de creación, lectura, actualización y borrado con SQLAlchemy, Prisma y Hibernate: el mismo modelo de datos en Python, JavaScript y Java.
GuidePooling de Conexiones a Base de Datos: Guía Práctica
Guía práctica sobre pooling de conexiones de base de datos: dimensionar pools, manejar timeouts de inactividad, detectar fugas, y configurar HikariCP, PgBouncer y pools nativos en la nube para máximo throughput.
GuideEstrategias de Indexación — Desde B-Trees hasta BRIN
Guía práctica de índices de bases de datos: B-Trees, Hash, GIN, GiST, BRIN e índices parciales. Aprende cuándo usar cada uno y cómo evitar errores comunes de indexación.